diff --git a/bom/application/pom.xml b/bom/application/pom.xml index e697541501d524..24d0103a142e22 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -3780,6 +3780,11 @@ quarkus-resteasy-common-spi ${project.version} + + io.quarkus + quarksu-resteasy-common-deployment-spi + ${project.version} + io.reactivex.rxjava2 rxjava diff --git a/extensions/pom.xml b/extensions/pom.xml index 7f86c1174b30f5..1b866dc2c2165c 100644 --- a/extensions/pom.xml +++ b/extensions/pom.xml @@ -59,6 +59,7 @@ resteasy-classic + resteasy-common smallrye-openapi-common smallrye-openapi swagger-ui diff --git a/extensions/resteasy-common/deployment-spi/pom.xml b/extensions/resteasy-common/deployment-spi/pom.xml new file mode 100644 index 00000000000000..d4378386eb930e --- /dev/null +++ b/extensions/resteasy-common/deployment-spi/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + + io.quarkus + quarkus-resteasy-common-spi-parent + 999-SNAPSHOT + + + quarksu-resteasy-common-deployment-spi + Quarkus - Resteasy - SPI - Deployment + + + + io.quarkus + quarkus-core-deployment + + + io.smallrye + jandex + + + + diff --git a/extensions/resteasy-common/deployment-spi/src/main/java/io/quarkus/resteasy/common/deployment/EndpointValidationPredicatesBuildItem.java b/extensions/resteasy-common/deployment-spi/src/main/java/io/quarkus/resteasy/common/deployment/EndpointValidationPredicatesBuildItem.java new file mode 100644 index 00000000000000..f3b9b7bc34fd09 --- /dev/null +++ b/extensions/resteasy-common/deployment-spi/src/main/java/io/quarkus/resteasy/common/deployment/EndpointValidationPredicatesBuildItem.java @@ -0,0 +1,20 @@ +package io.quarkus.resteasy.common.deployment; + +import java.util.function.Predicate; + +import org.jboss.jandex.ClassInfo; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class EndpointValidationPredicatesBuildItem extends MultiBuildItem { + + private final Predicate predicate; + + public EndpointValidationPredicatesBuildItem(Predicate predicate) { + this.predicate = predicate; + } + + public Predicate getPredicate() { + return predicate; + } +} diff --git a/extensions/resteasy-common/pom.xml b/extensions/resteasy-common/pom.xml new file mode 100644 index 00000000000000..86b66d80bd60a0 --- /dev/null +++ b/extensions/resteasy-common/pom.xml @@ -0,0 +1,20 @@ + + + + quarkus-extensions-parent + io.quarkus + 999-SNAPSHOT + ../pom.xml + + 4.0.0 + + quarkus-resteasy-common-spi-parent + Quarkus - JMS SPI - Parent + pom + + deployment-spi + + + diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml index bc8a8c616760aa..4c40a3460fdf30 100644 --- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml +++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/pom.xml @@ -33,6 +33,10 @@ io.quarkus quarkus-rest-spi-deployment + + io.quarkus + quarksu-resteasy-common-deployment-spi + io.quarkus quarkus-rest-client-jaxrs diff --git a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index 2f4ac2e80302f5..2f8b3722a6a67f 100644 --- a/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest-client-jaxrs/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -50,6 +50,7 @@ import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Pattern; +import java.util.stream.Collectors; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.RuntimeType; @@ -170,6 +171,7 @@ import io.quarkus.jaxrs.client.reactive.runtime.RestClientBase; import io.quarkus.jaxrs.client.reactive.runtime.ToObjectArray; import io.quarkus.jaxrs.client.reactive.runtime.impl.MultipartResponseDataBase; +import io.quarkus.resteasy.common.deployment.EndpointValidationPredicatesBuildItem; import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem; import io.quarkus.resteasy.reactive.common.deployment.ParameterContainersBuildItem; import io.quarkus.resteasy.reactive.common.deployment.QuarkusFactoryCreator; @@ -289,7 +291,8 @@ void setupClientProxies(JaxrsClientReactiveRecorder recorder, List defaultProduces, List disableSmartDefaultProduces, List disableRemovalTrailingSlashProduces, - List parameterContainersBuildItems) { + List parameterContainersBuildItems, + List validationPredicatesBuildItems) { String defaultConsumesType = defaultMediaType(defaultConsumes, MediaType.APPLICATION_OCTET_STREAM); String defaultProducesType = defaultMediaType(defaultProduces, MediaType.TEXT_PLAIN); @@ -343,6 +346,8 @@ public boolean test(Map anns) { return anns.containsKey(NOT_BODY) || anns.containsKey(URL); } }) + .setValidateEndpoint(validationPredicatesBuildItems.stream().map(item -> item.getPredicate()) + .collect(Collectors.toUnmodifiableList())) .setResourceMethodCallback(new Consumer<>() { @Override public void accept(EndpointIndexer.ResourceMethodCallbackEntry entry) { diff --git a/extensions/resteasy-reactive/rest/deployment/pom.xml b/extensions/resteasy-reactive/rest/deployment/pom.xml index a506c92c91c7a8..12b9cda33db485 100644 --- a/extensions/resteasy-reactive/rest/deployment/pom.xml +++ b/extensions/resteasy-reactive/rest/deployment/pom.xml @@ -46,6 +46,10 @@ io.quarkus quarkus-security-spi + + io.quarkus + quarksu-resteasy-common-deployment-spi + io.quarkus quarkus-jsonp-deployment diff --git a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java index 35ce4ab24bddea..0e47168c16a723 100644 --- a/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java +++ b/extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java @@ -14,6 +14,7 @@ import java.io.File; import java.io.IOException; +import java.lang.reflect.Modifier; import java.nio.file.Path; import java.util.ArrayDeque; import java.util.ArrayList; @@ -159,6 +160,7 @@ import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.netty.deployment.MinNettyAllocatorMaxOrderBuildItem; +import io.quarkus.resteasy.common.deployment.EndpointValidationPredicatesBuildItem; import io.quarkus.resteasy.reactive.common.deployment.AggregatedParameterContainersBuildItem; import io.quarkus.resteasy.reactive.common.deployment.ApplicationResultBuildItem; import io.quarkus.resteasy.reactive.common.deployment.FactoryUtils; @@ -468,7 +470,8 @@ public void setupEndpoints(ApplicationIndexBuildItem applicationIndexBuildItem, CompiledJavaVersionBuildItem compiledJavaVersionBuildItem, ResourceInterceptorsBuildItem resourceInterceptorsBuildItem, Capabilities capabilities, - Optional allowNotRestParametersBuildItem) { + Optional allowNotRestParametersBuildItem, + List validationPredicatesBuildItems) { if (!resourceScanningResultBuildItem.isPresent()) { // no detected @Path, bail out @@ -640,6 +643,8 @@ private boolean hasAnnotation(MethodInfo method, short paramPosition, DotName an }) .setResteasyReactiveRecorder(recorder) .setApplicationClassPredicate(applicationClassPredicate) + .setValidateEndpoint(validationPredicatesBuildItems.stream().map(item -> item.getPredicate()) + .collect(Collectors.toUnmodifiableList())) .setTargetJavaVersion(new TargetJavaVersion() { private final Status result; @@ -849,6 +854,18 @@ private FilterClassIntrospector createFilterClassIntrospector() { return ab.get(); } + @BuildStep + EndpointValidationPredicatesBuildItem createSpringRestControllerPredicate() { + Predicate predicate = new Predicate() { + @Override + public boolean test(ClassInfo classInfo) { + return Modifier.isInterface(classInfo.flags()) + || Modifier.isAbstract(classInfo.flags()); + } + }; + return new EndpointValidationPredicatesBuildItem(predicate); + } + // We want to add @Typed to resources, beanparams and providers so that they can be resolved as CDI bean using purely their // class as a bean type. This removes any ambiguity that potential subclasses may have. @BuildStep diff --git a/extensions/spring-web/core/deployment/pom.xml b/extensions/spring-web/core/deployment/pom.xml index 3e4a4c85b14145..59044073bf2c68 100644 --- a/extensions/spring-web/core/deployment/pom.xml +++ b/extensions/spring-web/core/deployment/pom.xml @@ -44,6 +44,10 @@ io.quarkus quarkus-resteasy-common-spi + + io.quarkus + quarksu-resteasy-common-deployment-spi + diff --git a/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java index 4699cc95931415..796bcef677ef0e 100644 --- a/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java +++ b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java @@ -6,6 +6,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Predicate; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.core.HttpHeaders; @@ -34,6 +35,7 @@ import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem; import io.quarkus.gizmo.ClassOutput; import io.quarkus.jaxrs.spi.deployment.AdditionalJaxRsResourceMethodAnnotationsBuildItem; +import io.quarkus.resteasy.common.deployment.EndpointValidationPredicatesBuildItem; import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem; @@ -86,6 +88,18 @@ public AdditionalJaxRsResourceMethodAnnotationsBuildItem additionalJaxRsResource return new AdditionalJaxRsResourceMethodAnnotationsBuildItem(MAPPING_ANNOTATIONS); } + @BuildStep + EndpointValidationPredicatesBuildItem createSpringRestControllerPredicate() { + Predicate predicate = new Predicate<>() { + @Override + public boolean test(ClassInfo classInfo) { + return classInfo + .declaredAnnotation(REST_CONTROLLER_ANNOTATION) == null; + } + }; + return new EndpointValidationPredicatesBuildItem(predicate); + } + @BuildStep public void ignoreReflectionHierarchy(BuildProducer ignore) { ignore.produce(new ReflectiveHierarchyIgnoreWarningBuildItem( diff --git a/extensions/spring-web/resteasy-reactive/tests/src/test/java/io/quarkus/spring/web/resteasy/reactive/test/UnbuildableSpringRestControllerInterfaceTest.java b/extensions/spring-web/resteasy-reactive/tests/src/test/java/io/quarkus/spring/web/resteasy/reactive/test/UnbuildableSpringRestControllerInterfaceTest.java new file mode 100644 index 00000000000000..9cdb6b44c87798 --- /dev/null +++ b/extensions/spring-web/resteasy-reactive/tests/src/test/java/io/quarkus/spring/web/resteasy/reactive/test/UnbuildableSpringRestControllerInterfaceTest.java @@ -0,0 +1,48 @@ +package io.quarkus.spring.web.resteasy.reactive.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +import jakarta.ws.rs.QueryParam; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import io.quarkus.test.QuarkusProdModeTest; + +public class UnbuildableSpringRestControllerInterfaceTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(UnbuildableControllerInterface.class)) + .setApplicationName("unbuildable-rest-controller-interface") + .setApplicationVersion("0.1-SNAPSHOT") + .assertBuildException(throwable -> { + assertThat(throwable).isInstanceOf(RuntimeException.class); + assertThat(throwable).hasMessageContaining( + "Cannot have more than one of @PathParam, @QueryParam, @HeaderParam, @FormParam, @CookieParam, @BeanParam, @Context on method"); + }); + + @Test + public void testBuildLogs() { + fail("Should not be called"); + } + + @RestController + @RequestMapping("/unbuildable") + public interface UnbuildableControllerInterface { + @GetMapping("/ping") + String ping(); + + @PostMapping("/hello") + String hello(@RequestParam(required = false) @QueryParam("dung") String params); + + } + +} diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java index 25633cdd60993d..63aa10ffb1b6b1 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/EndpointIndexer.java @@ -239,6 +239,7 @@ public abstract class EndpointIndexer> isDisabledCreator; private final Predicate> skipMethodParameter; + private final List> validateEndpoint; private final boolean skipNotRestParameters; private SerializerScanningResult serializerScanningResult; @@ -267,6 +268,7 @@ protected EndpointIndexer(Builder builder) { this.isDisabledCreator = builder.isDisabledCreator; this.skipMethodParameter = builder.skipMethodParameter; this.skipNotRestParameters = builder.skipNotRestParameters; + this.validateEndpoint = builder.validateEndpoint; } public Optional createEndpoints(ClassInfo classInfo, boolean considerApplication) { @@ -324,14 +326,18 @@ public Optional createEndpoints(ClassInfo classInfo, boolean cons return Optional.of(clazz); } catch (Exception e) { - if (Modifier.isInterface(classInfo.flags()) || Modifier.isAbstract(classInfo.flags())) { - //kinda bogus, but we just ignore failed interfaces for now - //they can have methods that are not valid until they are actually extended by a concrete type - log.debug("Ignoring interface " + classInfo.name(), e); - return Optional.empty(); + for (Predicate predicate : validateEndpoint) { + if (predicate.test(classInfo)) { + //kinda bogus, but we just ignore failed interfaces for now + //they can have methods that are not valid until they are actually extended by a concrete type + log.debug("Ignoring interface " + classInfo.name(), e); + } else { + throw new RuntimeException(e); + } } - throw new RuntimeException(e); + return Optional.empty(); } + } private String sanitizePath(String path) { @@ -1708,6 +1714,7 @@ public boolean handleMultipartForReturnType(AdditionalWriters additionalWriters, private Function> isDisabledCreator = null; private Predicate> skipMethodParameter = null; + private List> validateEndpoint = null; private boolean skipNotRestParameters = false; @@ -1844,6 +1851,11 @@ public B setSkipMethodParameter( return (B) this; } + public B setValidateEndpoint(List> validateEndpoint) { + this.validateEndpoint = validateEndpoint; + return (B) this; + } + public B skipNotRestParameters(boolean skipNotRestParameters) { this.skipNotRestParameters = skipNotRestParameters; return (B) this;