From e416cae58a10c46fbab787081a4b8371629ac3a6 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 30 Oct 2024 16:19:40 +0200 Subject: [PATCH] Properly handle `@ApplicationPath` and `Application` in RESTEasy Classic `@ApplicationPath` is only taken into account for the single `Application` class that is selected (if any). --- .../ResteasyServerCommonProcessor.java | 112 +++++++----------- .../resteasy/test/root/ApplicationTest.java | 2 + 2 files changed, 44 insertions(+), 70 deletions(-) diff --git a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java index f72456f4a6460..5f2110716dc54 100644 --- a/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java +++ b/extensions/resteasy-classic/resteasy-server-common/deployment/src/main/java/io/quarkus/resteasy/server/common/deployment/ResteasyServerCommonProcessor.java @@ -222,44 +222,44 @@ public void build( CustomScopeAnnotationsBuildItem scopes) throws Exception { IndexView index = combinedIndexBuildItem.getIndex(); - Collection applicationPaths = Collections.emptySet(); - final Set allowedClasses; + final AnnotationInstance applicationPath; final Set excludedClasses; if (resteasyConfig.buildTimeConditionAware) { excludedClasses = getExcludedClasses(buildTimeConditions); } else { excludedClasses = Collections.emptySet(); } + final Set allowedClasses; final String appClass; if (resteasyConfig.ignoreApplicationClasses) { + applicationPath = null; allowedClasses = Collections.emptySet(); appClass = null; } else { - applicationPaths = index.getAnnotations(ResteasyDotNames.APPLICATION_PATH); - allowedClasses = getAllowedClasses(index); - jaxrsProvidersToRegisterBuildItem = getFilteredJaxrsProvidersToRegisterBuildItem( - jaxrsProvidersToRegisterBuildItem, allowedClasses, excludedClasses); - - Collection knownApplications = index.getAllKnownSubclasses(ResteasyDotNames.APPLICATION).stream() + Collection jakartaRestApplicationClasses = index.getAllKnownSubclasses(ResteasyDotNames.APPLICATION) + .stream() .filter(ci -> !ci.isAbstract()).collect( Collectors.toSet()); - // getAllowedClasses throws an Exception if multiple Applications are found, so we should only get 1 - if (knownApplications.size() == 1) { - appClass = knownApplications.iterator().next().name().toString(); - } else { + if (jakartaRestApplicationClasses.size() > 1) { + throw new RuntimeException("More than one Application class: " + jakartaRestApplicationClasses); + } + if (jakartaRestApplicationClasses.isEmpty()) { + applicationPath = null; + allowedClasses = Collections.emptySet(); appClass = null; + } else { + ClassInfo jakartaRestApplicationClass = jakartaRestApplicationClasses.iterator().next(); + applicationPath = jakartaRestApplicationClass.annotation(ResteasyDotNames.APPLICATION_PATH); + allowedClasses = getAllowedClasses(jakartaRestApplicationClass); + appClass = jakartaRestApplicationClass.name().toString(); } + + jaxrsProvidersToRegisterBuildItem = getFilteredJaxrsProvidersToRegisterBuildItem( + jaxrsProvidersToRegisterBuildItem, allowedClasses, excludedClasses); } boolean filterClasses = !allowedClasses.isEmpty() || !excludedClasses.isEmpty(); - // currently we only examine the first class that is annotated with @ApplicationPath so best - // fail if the user code has multiple such annotations instead of surprising the user - // at runtime - if (applicationPaths.size() > 1) { - throw createMultipleApplicationsException(applicationPaths); - } - Set additionalPaths = new HashSet<>(); for (AdditionalJaxRsResourceDefiningAnnotationBuildItem annotation : additionalJaxRsResourceDefiningAnnotations) { additionalPaths.addAll(beanArchiveIndexBuildItem.getIndex().getAnnotations(annotation.getAnnotationClass())); @@ -284,8 +284,7 @@ public void build( final String rootPath; final String path; - if (!applicationPaths.isEmpty()) { - AnnotationInstance applicationPath = applicationPaths.iterator().next(); + if (applicationPath != null) { rootPath = "/"; path = Encode.decode(applicationPath.value().asString()); } else { @@ -1003,21 +1002,6 @@ private static boolean hasAnnotation(MethodInfo method, short paramPosition, Dot return false; } - private static RuntimeException createMultipleApplicationsException(Collection applicationPaths) { - StringBuilder sb = new StringBuilder(); - boolean first = true; - for (AnnotationInstance annotationInstance : applicationPaths) { - if (first) { - first = false; - } else { - sb.append(","); - } - sb.append(annotationInstance.target().asClass().name().toString()); - } - return new RuntimeException("Multiple classes ( " + sb.toString() - + ") have been annotated with @ApplicationPath which is currently not supported"); - } - /** * @param buildTimeConditions the build time conditions from which the excluded classes are extracted. * @return the set of classes that have been annotated with unsuccessful build time conditions. @@ -1098,49 +1082,37 @@ private static JaxrsProvidersToRegisterBuildItem getFilteredJaxrsProvidersToRegi } /** - * @param index the index to use to find the existing {@link Application}. * @return the set of classes returned by the methods {@link Application#getClasses()} and * {@link Application#getSingletons()}. */ - private static Set getAllowedClasses(IndexView index) { - final Collection applications = index.getAllKnownSubclasses(ResteasyDotNames.APPLICATION); + private Set getAllowedClasses(ClassInfo jakartaRestApplicationClass) { final Set allowedClasses = new HashSet<>(); Application application; - ClassInfo selectedAppClass = null; - for (ClassInfo applicationClassInfo : applications) { - if (Modifier.isAbstract(applicationClassInfo.flags())) { - continue; - } - if (selectedAppClass != null) { - throw new RuntimeException("More than one Application class: " + applications); - } - selectedAppClass = applicationClassInfo; - if (selectedAppClass.annotationsMap().containsKey(ResteasyDotNames.CDI_INJECT)) { - throw new RuntimeException( - "Usage of '@Inject' is not allowed in 'jakarta.ws.rs.core.Application' classes. Offending class is '" - + selectedAppClass.name() + "'"); - } + if (jakartaRestApplicationClass.annotationsMap().containsKey(ResteasyDotNames.CDI_INJECT)) { + throw new RuntimeException( + "Usage of '@Inject' is not allowed in 'jakarta.ws.rs.core.Application' classes. Offending class is '" + + jakartaRestApplicationClass.name() + "'"); + } - String applicationClass = applicationClassInfo.name().toString(); - try { - Class appClass = Thread.currentThread().getContextClassLoader().loadClass(applicationClass); - application = (Application) appClass.getConstructor().newInstance(); - Set> classes = application.getClasses(); - if (!classes.isEmpty()) { - for (Class klass : classes) { - allowedClasses.add(klass.getName()); - } + String applicationClass = jakartaRestApplicationClass.name().toString(); + try { + Class appClass = Thread.currentThread().getContextClassLoader().loadClass(applicationClass); + application = (Application) appClass.getConstructor().newInstance(); + Set> classes = application.getClasses(); + if (!classes.isEmpty()) { + for (Class klass : classes) { + allowedClasses.add(klass.getName()); } - classes = application.getSingletons().stream().map(Object::getClass).collect(Collectors.toSet()); - if (!classes.isEmpty()) { - for (Class klass : classes) { - allowedClasses.add(klass.getName()); - } + } + classes = application.getSingletons().stream().map(Object::getClass).collect(Collectors.toSet()); + if (!classes.isEmpty()) { + for (Class klass : classes) { + allowedClasses.add(klass.getName()); } - } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException - | InvocationTargetException e) { - throw new RuntimeException("Unable to handle class: " + applicationClass, e); } + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException + | InvocationTargetException e) { + throw new RuntimeException("Unable to handle class: " + applicationClass, e); } return allowedClasses; } diff --git a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationTest.java b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationTest.java index 22646dea2e296..9d9d7dcabb0dc 100644 --- a/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationTest.java +++ b/extensions/resteasy-classic/resteasy/deployment/src/test/java/io/quarkus/resteasy/test/root/ApplicationTest.java @@ -9,6 +9,7 @@ import java.util.HashSet; import java.util.Set; +import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.container.*; @@ -310,6 +311,7 @@ public Set getSingletons() { } } + @ApplicationPath("whatever") public static abstract class AppTest2 extends Application { }