diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index e1aee7119e11e..02a044996b4f4 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -1788,6 +1788,11 @@
quarkus-spring-scheduled-deployment
${project.version}
+
+ io.quarkus
+ quarkus-spring-web-common
+ ${project.version}
+
io.quarkus
quarkus-spring-web
@@ -1798,6 +1803,16 @@
quarkus-spring-web-deployment
${project.version}
+
+ io.quarkus
+ quarkus-spring-web-resteasy-classic
+ ${project.version}
+
+
+ io.quarkus
+ quarkus-spring-web-resteasy-classic-deployment
+ ${project.version}
+
io.quarkus
quarkus-spring-data-jpa
diff --git a/devtools/bom-descriptor-json/pom.xml b/devtools/bom-descriptor-json/pom.xml
index 5572c44fd07a2..7b48d90c97e05 100644
--- a/devtools/bom-descriptor-json/pom.xml
+++ b/devtools/bom-descriptor-json/pom.xml
@@ -2671,6 +2671,19 @@
+
+ io.quarkus
+ quarkus-spring-web-resteasy-classic
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-swagger-ui
diff --git a/docs/pom.xml b/docs/pom.xml
index ecf2adf740e0f..f591ade57a3f8 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -2632,6 +2632,19 @@
+
+ io.quarkus
+ quarkus-spring-web-resteasy-classic-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-swagger-ui-deployment
diff --git a/extensions/spring-web/core/common-runtime/pom.xml b/extensions/spring-web/core/common-runtime/pom.xml
new file mode 100644
index 0000000000000..11e227f56b883
--- /dev/null
+++ b/extensions/spring-web/core/common-runtime/pom.xml
@@ -0,0 +1,42 @@
+
+
+
+ quarkus-spring-web-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+
+ 4.0.0
+
+ quarkus-spring-web-common
+ Quarkus - Spring Web - Common Runtime
+
+
+
+ org.jboss.spec.javax.ws.rs
+ jboss-jaxrs-api_2.1_spec
+
+
+ jakarta.enterprise
+ jakarta.enterprise.cdi-api
+
+
+ io.quarkus
+ quarkus-spring-web-api
+
+
+ io.quarkus
+ quarkus-spring-webmvc-api
+
+
+ io.quarkus
+ quarkus-spring-core-api
+
+
+ io.quarkus
+ quarkus-spring-context-api
+
+
+
diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseContentTypeResolver.java b/extensions/spring-web/core/common-runtime/src/main/java/io.quarkus.spring.web.runtime.common/AbstractResponseContentTypeResolver.java
similarity index 73%
rename from extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseContentTypeResolver.java
rename to extensions/spring-web/core/common-runtime/src/main/java/io.quarkus.spring.web.runtime.common/AbstractResponseContentTypeResolver.java
index db25c1729403c..b8429a0dc7c19 100644
--- a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseContentTypeResolver.java
+++ b/extensions/spring-web/core/common-runtime/src/main/java/io.quarkus.spring.web.runtime.common/AbstractResponseContentTypeResolver.java
@@ -1,4 +1,4 @@
-package io.quarkus.spring.web.runtime;
+package io.quarkus.spring.web.runtime.common;
import static javax.ws.rs.core.HttpHeaders.ACCEPT;
import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE;
@@ -10,13 +10,13 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Variant;
-import org.jboss.resteasy.core.request.ServerDrivenNegotiation;
-
-public final class ResponseContentTypeResolver {
+public abstract class AbstractResponseContentTypeResolver {
private static final MediaType DEFAULT_MEDIA_TYPE = TEXT_PLAIN_TYPE;
- public static MediaType resolve(HttpHeaders httpHeaders, String... supportedMediaTypes) {
+ protected abstract Variant negotiateBestMatch(List acceptHeaders, List variants);
+
+ public MediaType resolve(HttpHeaders httpHeaders, String... supportedMediaTypes) {
Objects.requireNonNull(httpHeaders, "HttpHeaders cannot be null");
Objects.requireNonNull(supportedMediaTypes, "Supported media types array cannot be null");
@@ -33,16 +33,13 @@ public static MediaType resolve(HttpHeaders httpHeaders, String... supportedMedi
return DEFAULT_MEDIA_TYPE;
}
- private static Variant getBestVariant(List acceptHeaders, List variants) {
+ private Variant getBestVariant(List acceptHeaders, List variants) {
if (acceptHeaders.isEmpty()) {
// done because negotiation.setAcceptHeaders(acceptHeaders) throws a NPE when passed an empty list
return null;
}
- ServerDrivenNegotiation negotiation = new ServerDrivenNegotiation();
- negotiation.setAcceptHeaders(acceptHeaders);
-
- return negotiation.getBestMatch(variants);
+ return negotiateBestMatch(acceptHeaders, variants);
}
private static List getMediaTypeVariants(String... mediaTypes) {
diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseStatusExceptionMapper.java b/extensions/spring-web/core/common-runtime/src/main/java/io.quarkus.spring.web.runtime.common/AbstractResponseStatusExceptionMapper.java
similarity index 71%
rename from extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseStatusExceptionMapper.java
rename to extensions/spring-web/core/common-runtime/src/main/java/io.quarkus.spring.web.runtime.common/AbstractResponseStatusExceptionMapper.java
index 2a3f11e997ab5..9ebb33781bf9b 100644
--- a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseStatusExceptionMapper.java
+++ b/extensions/spring-web/core/common-runtime/src/main/java/io.quarkus.spring.web.runtime.common/AbstractResponseStatusExceptionMapper.java
@@ -1,4 +1,4 @@
-package io.quarkus.spring.web.runtime;
+package io.quarkus.spring.web.runtime.common;
import java.util.List;
import java.util.Map;
@@ -7,15 +7,16 @@
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
-import org.jboss.resteasy.specimpl.ResponseBuilderImpl;
import org.springframework.http.HttpHeaders;
import org.springframework.web.server.ResponseStatusException;
-public class ResponseStatusExceptionMapper implements ExceptionMapper {
+public abstract class AbstractResponseStatusExceptionMapper implements ExceptionMapper {
+
+ protected abstract Response.ResponseBuilder createResponseBuilder(ResponseStatusException exception);
@Override
public Response toResponse(ResponseStatusException exception) {
- Response.ResponseBuilder responseBuilder = new ResponseBuilderImpl().status(exception.getStatus().value());
+ Response.ResponseBuilder responseBuilder = createResponseBuilder(exception);
addHeaders(responseBuilder, exception.getResponseHeaders());
return responseBuilder.entity(exception.getMessage())
.type(MediaType.TEXT_PLAIN).build();
diff --git a/extensions/spring-web/deployment/pom.xml b/extensions/spring-web/core/deployment/pom.xml
similarity index 92%
rename from extensions/spring-web/deployment/pom.xml
rename to extensions/spring-web/core/deployment/pom.xml
index 17ad4a9d67ee8..3b92b540a16ad 100644
--- a/extensions/spring-web/deployment/pom.xml
+++ b/extensions/spring-web/core/deployment/pom.xml
@@ -11,7 +11,7 @@
4.0.0
quarkus-spring-web-deployment
- Quarkus - Spring - Web - Deployment
+ Quarkus - Spring Web - Deployment
@@ -24,7 +24,8 @@
io.quarkus
- quarkus-resteasy-jackson-deployment
+ quarkus-spring-web-resteasy-classic-deployment
+ true
io.quarkus
@@ -32,7 +33,8 @@
io.quarkus
- quarkus-resteasy-common-spi
+ quarkus-resteasy-jackson-deployment
+ test
io.quarkus
diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java
similarity index 100%
rename from extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java
rename to extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/AbstractExceptionMapperGenerator.java
diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
similarity index 92%
rename from extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
rename to extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
index 346407d9ca411..f3c0a5ce9a113 100644
--- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
+++ b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/ControllerAdviceAbstractExceptionMapperGenerator.java
@@ -29,7 +29,6 @@
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
-import io.quarkus.spring.web.runtime.ResponseContentTypeResolver;
import io.quarkus.spring.web.runtime.ResponseEntityConverter;
class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractExceptionMapperGenerator {
@@ -53,15 +52,24 @@ class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractException
private FieldDescriptor httpHeadersField;
+ private final boolean isResteasyClassic;
+
ControllerAdviceAbstractExceptionMapperGenerator(MethodInfo controllerAdviceMethod, DotName exceptionDotName,
- ClassOutput classOutput, TypesUtil typesUtil) {
+ ClassOutput classOutput, TypesUtil typesUtil, boolean isResteasyClassic) {
super(exceptionDotName, classOutput);
+
+ // TODO: remove this restriction
+ if (!isResteasyClassic) {
+ throw new IllegalStateException("Currently Spring Web can only work with RESTEasy Classic");
+ }
+
this.controllerAdviceMethod = controllerAdviceMethod;
this.typesUtil = typesUtil;
this.returnType = controllerAdviceMethod.returnType();
this.parameterTypes = controllerAdviceMethod.parameters();
this.declaringClassName = controllerAdviceMethod.declaringClass().name().toString();
+ this.isResteasyClassic = isResteasyClassic;
}
/**
@@ -187,9 +195,15 @@ private ResultHandle getResponseContentType(MethodCreator methodCreator, List MAPPING_ANNOTATIONS;
+
+ static {
+ MAPPING_ANNOTATIONS = Arrays.asList(
+ REQUEST_MAPPING,
+ DotName.createSimple("org.springframework.web.bind.annotation.GetMapping"),
+ DotName.createSimple("org.springframework.web.bind.annotation.PostMapping"),
+ DotName.createSimple("org.springframework.web.bind.annotation.PutMapping"),
+ DotName.createSimple("org.springframework.web.bind.annotation.DeleteMapping"),
+ DotName.createSimple("org.springframework.web.bind.annotation.PatchMapping"));
+ }
+
+ private static final DotName RESPONSE_STATUS = DotName
+ .createSimple("org.springframework.web.bind.annotation.ResponseStatus");
+ private static final DotName EXCEPTION_HANDLER = DotName
+ .createSimple("org.springframework.web.bind.annotation.ExceptionHandler");
+
+ private static final DotName REST_CONTROLLER_ADVICE = DotName
+ .createSimple("org.springframework.web.bind.annotation.RestControllerAdvice");
+
+ private static final DotName MODEL_AND_VIEW = DotName.createSimple("org.springframework.web.servlet.ModelAndView");
+ private static final DotName VIEW = DotName.createSimple("org.springframework.web.servlet.View");
+ private static final DotName MODEL = DotName.createSimple("org.springframework.ui.Model");
+
+ private static final DotName HTTP_ENTITY = DotName.createSimple("org.springframework.http.HttpEntity");
+ private static final DotName RESPONSE_ENTITY = DotName.createSimple("org.springframework.http.ResponseEntity");
+
+ private static final Set DISALLOWED_EXCEPTION_CONTROLLER_RETURN_TYPES = new HashSet<>(Arrays.asList(
+ MODEL_AND_VIEW, VIEW, MODEL, HTTP_ENTITY));
+
+ @BuildStep
+ FeatureBuildItem registerFeature() {
+ return new FeatureBuildItem(Feature.SPRING_WEB);
+ }
+
+ @BuildStep
+ public AdditionalJaxRsResourceMethodAnnotationsBuildItem additionalJaxRsResourceMethodAnnotationsBuildItem() {
+ return new AdditionalJaxRsResourceMethodAnnotationsBuildItem(MAPPING_ANNOTATIONS);
+ }
+
+ @BuildStep
+ public void ignoreReflectionHierarchy(BuildProducer ignore) {
+ ignore.produce(new ReflectiveHierarchyIgnoreWarningBuildItem(
+ new ReflectiveHierarchyIgnoreWarningBuildItem.DotNameExclusion(RESPONSE_ENTITY)));
+ ignore.produce(
+ new ReflectiveHierarchyIgnoreWarningBuildItem(new ReflectiveHierarchyIgnoreWarningBuildItem.DotNameExclusion(
+ DotName.createSimple("org.springframework.util.MimeType"))));
+ ignore.produce(
+ new ReflectiveHierarchyIgnoreWarningBuildItem(new ReflectiveHierarchyIgnoreWarningBuildItem.DotNameExclusion(
+ DotName.createSimple("org.springframework.util.MultiValueMap"))));
+ }
+
+ @BuildStep
+ public void beanDefiningAnnotations(BuildProducer beanDefiningAnnotations) {
+ beanDefiningAnnotations
+ .produce(new BeanDefiningAnnotationBuildItem(REST_CONTROLLER_ANNOTATION, BuiltinScope.SINGLETON.getName()));
+ beanDefiningAnnotations
+ .produce(new BeanDefiningAnnotationBuildItem(REST_CONTROLLER_ADVICE, BuiltinScope.SINGLETON.getName()));
+ }
+
+ @BuildStep
+ public void generateExceptionMapperProviders(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
+ BuildProducer generatedExceptionMappers,
+ BuildProducer providersProducer,
+ BuildProducer reflectiveClassProducer,
+ Capabilities capabilities) {
+
+ boolean isResteasyClassicAvailable = capabilities.isPresent(Capability.RESTEASY_JSON_JACKSON);
+ boolean isResteasyReactiveAvailable = capabilities.isPresent(Capability.RESTEASY_REACTIVE_JSON_JACKSON);
+
+ if (!isResteasyClassicAvailable && !isResteasyReactiveAvailable) {
+ throw new IllegalStateException(
+ "Spring Web can only work if 'quarkus-resteasy-jackson' or 'quarkus-resteasy-reactive-jackson' is present");
+ }
+
+ TypesUtil typesUtil = new TypesUtil(Thread.currentThread().getContextClassLoader());
+
+ // Look for all exception classes that are annotated with @ResponseStatus
+
+ IndexView index = beanArchiveIndexBuildItem.getIndex();
+ ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedExceptionMappers, true);
+ generateMappersForResponseStatusOnException(providersProducer, index, classOutput, typesUtil,
+ isResteasyClassicAvailable);
+ generateMappersForExceptionHandlerInControllerAdvice(providersProducer, reflectiveClassProducer, index, classOutput,
+ typesUtil, isResteasyClassicAvailable);
+ }
+
+ private void generateMappersForResponseStatusOnException(BuildProducer providersProducer,
+ IndexView index, ClassOutput classOutput, TypesUtil typesUtil, boolean isResteasyClassic) {
+ Collection responseStatusInstances = index
+ .getAnnotations(RESPONSE_STATUS);
+
+ if (responseStatusInstances.isEmpty()) {
+ return;
+ }
+
+ for (AnnotationInstance instance : responseStatusInstances) {
+ if (AnnotationTarget.Kind.CLASS != instance.target().kind()) {
+ continue;
+ }
+ if (!typesUtil.isAssignable(Exception.class, instance.target().asClass().name())) {
+ continue;
+ }
+
+ String name = new ResponseStatusOnExceptionGenerator(instance.target().asClass(), classOutput, isResteasyClassic)
+ .generate();
+ providersProducer.produce(new ResteasyJaxrsProviderBuildItem(name));
+ }
+ }
+
+ private void generateMappersForExceptionHandlerInControllerAdvice(
+ BuildProducer providersProducer,
+ BuildProducer reflectiveClassProducer, IndexView index, ClassOutput classOutput,
+ TypesUtil typesUtil, boolean isResteasyClassic) {
+
+ AnnotationInstance controllerAdviceInstance = getSingleControllerAdviceInstance(index);
+ if (controllerAdviceInstance == null) {
+ return;
+ }
+
+ ClassInfo controllerAdvice = controllerAdviceInstance.target().asClass();
+ List methods = controllerAdvice.methods();
+ for (MethodInfo method : methods) {
+ AnnotationInstance exceptionHandlerInstance = method.annotation(EXCEPTION_HANDLER);
+ if (exceptionHandlerInstance == null) {
+ continue;
+ }
+
+ if (!Modifier.isPublic(method.flags()) || Modifier.isStatic(method.flags())) {
+ throw new IllegalStateException(
+ "@ExceptionHandler methods in @ControllerAdvice must be public instance methods");
+ }
+
+ DotName returnTypeDotName = method.returnType().name();
+ if (DISALLOWED_EXCEPTION_CONTROLLER_RETURN_TYPES.contains(returnTypeDotName)) {
+ throw new IllegalStateException(
+ "@ExceptionHandler methods in @ControllerAdvice classes can only have void, ResponseEntity or POJO return types");
+ }
+
+ if (!RESPONSE_ENTITY.equals(returnTypeDotName)) {
+ reflectiveClassProducer.produce(new ReflectiveClassBuildItem(true, true, returnTypeDotName.toString()));
+ }
+
+ // we need to generate one JAX-RS ExceptionMapper per Exception type
+ Type[] handledExceptionTypes = exceptionHandlerInstance.value().asClassArray();
+ for (Type handledExceptionType : handledExceptionTypes) {
+ String name = new ControllerAdviceAbstractExceptionMapperGenerator(method, handledExceptionType.name(),
+ classOutput, typesUtil, isResteasyClassic).generate();
+ providersProducer.produce(new ResteasyJaxrsProviderBuildItem(name));
+ }
+
+ }
+ }
+
+ private AnnotationInstance getSingleControllerAdviceInstance(IndexView index) {
+ Collection controllerAdviceInstances = index.getAnnotations(REST_CONTROLLER_ADVICE);
+
+ if (controllerAdviceInstances.isEmpty()) {
+ return null;
+ }
+
+ if (controllerAdviceInstances.size() > 1) {
+ throw new IllegalStateException("You can only have a single class annotated with @ControllerAdvice");
+ }
+
+ return controllerAdviceInstances.iterator().next();
+ }
+
+}
diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/TypesUtil.java b/extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/TypesUtil.java
similarity index 100%
rename from extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/TypesUtil.java
rename to extensions/spring-web/core/deployment/src/main/java/io/quarkus/spring/web/deployment/TypesUtil.java
diff --git a/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java b/extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java
similarity index 100%
rename from extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java
rename to extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/ControllerReloadTest.java
diff --git a/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ResponseStatusAndExceptionHandlerTest.java b/extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/ResponseStatusAndExceptionHandlerTest.java
similarity index 100%
rename from extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/ResponseStatusAndExceptionHandlerTest.java
rename to extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/ResponseStatusAndExceptionHandlerTest.java
diff --git a/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringController.java b/extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringController.java
similarity index 100%
rename from extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringController.java
rename to extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringController.java
diff --git a/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringControllerTest.java b/extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringControllerTest.java
similarity index 100%
rename from extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringControllerTest.java
rename to extensions/spring-web/core/deployment/src/test/java/io/quarkus/spring/web/test/SimpleSpringControllerTest.java
diff --git a/extensions/spring-web/core/pom.xml b/extensions/spring-web/core/pom.xml
new file mode 100644
index 0000000000000..8b104bd2b4631
--- /dev/null
+++ b/extensions/spring-web/core/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ quarkus-spring-web-parent-aggregator
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-spring-web-parent
+ Quarkus - Spring Web
+ pom
+
+ deployment
+ runtime
+ common-runtime
+
+
+
+
diff --git a/extensions/spring-web/runtime/pom.xml b/extensions/spring-web/core/runtime/pom.xml
similarity index 52%
rename from extensions/spring-web/runtime/pom.xml
rename to extensions/spring-web/core/runtime/pom.xml
index 68f88fa22c41e..1f5a06b60b1cf 100644
--- a/extensions/spring-web/runtime/pom.xml
+++ b/extensions/spring-web/core/runtime/pom.xml
@@ -11,51 +11,18 @@
4.0.0
quarkus-spring-web
- Quarkus - Spring - Web - Runtime
+ Quarkus - Spring Web - Runtime
Use Spring Web annotations to create your REST services
io.quarkus
- quarkus-resteasy-jackson
-
-
- org.jboss.resteasy
- resteasy-spring-web
-
-
- javax.enterprise
- cdi-api
-
-
- jakarta.activation
- jakarta.activation-api
-
-
-
-
- jakarta.enterprise
- jakarta.enterprise.cdi-api
+ quarkus-spring-web-resteasy-classic
+ true
io.quarkus
quarkus-spring-di
-
- io.quarkus
- quarkus-spring-web-api
-
-
- io.quarkus
- quarkus-spring-webmvc-api
-
-
- io.quarkus
- quarkus-spring-core-api
-
-
- io.quarkus
- quarkus-spring-context-api
-
diff --git a/extensions/spring-web/core/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java b/extensions/spring-web/core/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java
new file mode 100644
index 0000000000000..d96c70de6405e
--- /dev/null
+++ b/extensions/spring-web/core/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java
@@ -0,0 +1,44 @@
+package io.quarkus.spring.web.runtime;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+
+import org.jboss.resteasy.specimpl.MultivaluedTreeMap;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.ResponseEntity;
+
+/**
+ * This is only used in the generated ExceptionMappers when the Spring @RestControllerAdvice method returns a ResponseEntity
+ */
+public class ResponseEntityConverter {
+
+ private static final String[] EMPTY_STRINGS_ARRAY = new String[0];
+
+ @SuppressWarnings("rawtypes")
+ public static Response toResponse(ResponseEntity responseEntity, MediaType defaultMediaType) {
+ Response.ResponseBuilder responseBuilder = Response.status(responseEntity.getStatusCodeValue())
+ .entity(responseEntity.getBody());
+ MultivaluedMap jaxRsHeaders = toJaxRsHeaders(responseEntity.getHeaders());
+ if (!jaxRsHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
+ jaxRsHeaders.putSingle(HttpHeaders.CONTENT_TYPE, defaultMediaType.toString());
+ }
+ for (var entry : jaxRsHeaders.entrySet()) {
+ var value = entry.getValue();
+ if (value.size() == 1) {
+ responseBuilder.header(entry.getKey(), entry.getValue().get(0));
+ } else {
+ responseBuilder.header(entry.getKey(), entry.getValue());
+ }
+ }
+ return responseBuilder.build();
+ }
+
+ private static MultivaluedMap toJaxRsHeaders(HttpHeaders springHeaders) {
+ var jaxRsHeaders = new MultivaluedTreeMap();
+ for (var entry : springHeaders.entrySet()) {
+ jaxRsHeaders.addAll(entry.getKey(), entry.getValue().toArray(EMPTY_STRINGS_ARRAY));
+ }
+ return jaxRsHeaders;
+ }
+}
diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/SpringWebEndpointProvider.java b/extensions/spring-web/core/runtime/src/main/java/io/quarkus/spring/web/runtime/SpringWebEndpointProvider.java
similarity index 100%
rename from extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/SpringWebEndpointProvider.java
rename to extensions/spring-web/core/runtime/src/main/java/io/quarkus/spring/web/runtime/SpringWebEndpointProvider.java
diff --git a/extensions/spring-web/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/spring-web/core/runtime/src/main/resources/META-INF/quarkus-extension.yaml
similarity index 100%
rename from extensions/spring-web/runtime/src/main/resources/META-INF/quarkus-extension.yaml
rename to extensions/spring-web/core/runtime/src/main/resources/META-INF/quarkus-extension.yaml
diff --git a/extensions/spring-web/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider b/extensions/spring-web/core/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider
similarity index 100%
rename from extensions/spring-web/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider
rename to extensions/spring-web/core/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider
diff --git a/extensions/spring-web/pom.xml b/extensions/spring-web/pom.xml
index b287523b966fc..4d142216408aa 100644
--- a/extensions/spring-web/pom.xml
+++ b/extensions/spring-web/pom.xml
@@ -10,11 +10,11 @@
4.0.0
- quarkus-spring-web-parent
- Quarkus - Spring - Web
+ quarkus-spring-web-parent-aggregator
+ Quarkus - Spring Web - Parent - Aggregator
pom
- deployment
- runtime
+ core
+ resteasy-classic
diff --git a/extensions/spring-web/resteasy-classic/deployment/pom.xml b/extensions/spring-web/resteasy-classic/deployment/pom.xml
new file mode 100644
index 0000000000000..21e15377874eb
--- /dev/null
+++ b/extensions/spring-web/resteasy-classic/deployment/pom.xml
@@ -0,0 +1,69 @@
+
+
+
+ quarkus-spring-web-resteasy-classic-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+
+ 4.0.0
+
+ quarkus-spring-web-resteasy-classic-deployment
+ Quarkus - Spring Web - RESTEasy Classic - Deployment
+
+
+
+ io.quarkus
+ quarkus-resteasy-deployment
+
+
+ io.quarkus
+ quarkus-resteasy-common-spi
+
+
+
+ io.quarkus
+ quarkus-spring-web-resteasy-classic
+
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ io.quarkus
+ quarkus-spring-web-api
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+
+
+
+ io.quarkus
+ quarkus-extension-processor
+ ${project.version}
+
+
+
+
+
+
+
diff --git a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java b/extensions/spring-web/resteasy-classic/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyClassicProcessor.java
similarity index 61%
rename from extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
rename to extensions/spring-web/resteasy-classic/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyClassicProcessor.java
index c68006e551a11..d71c414135bec 100644
--- a/extensions/spring-web/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebProcessor.java
+++ b/extensions/spring-web/resteasy-classic/deployment/src/main/java/io/quarkus/spring/web/deployment/SpringWebResteasyClassicProcessor.java
@@ -3,9 +3,7 @@
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
import java.io.IOException;
-import java.lang.reflect.Modifier;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
@@ -24,7 +22,6 @@
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
-import org.jboss.jandex.Type;
import org.jboss.logging.Logger;
import org.jboss.resteasy.core.MediaTypeMap;
import org.jboss.resteasy.plugins.server.servlet.ResteasyContextParameters;
@@ -34,21 +31,12 @@
import org.jboss.resteasy.spring.web.ResponseStatusFeature;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
-import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
-import io.quarkus.arc.processor.BuiltinScope;
-import io.quarkus.deployment.Feature;
-import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
-import io.quarkus.deployment.builditem.FeatureBuildItem;
-import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
-import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyIgnoreWarningBuildItem;
import io.quarkus.deployment.util.ServiceUtil;
-import io.quarkus.gizmo.ClassOutput;
-import io.quarkus.jaxrs.spi.deployment.AdditionalJaxRsResourceMethodAnnotationsBuildItem;
import io.quarkus.resteasy.common.deployment.ResteasyCommonProcessor;
import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem;
import io.quarkus.resteasy.runtime.ExceptionMapperRecorder;
@@ -56,55 +44,28 @@
import io.quarkus.resteasy.server.common.deployment.ResteasyDeploymentCustomizerBuildItem;
import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceDefiningAnnotationBuildItem;
import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceMethodParamAnnotations;
-import io.quarkus.spring.web.runtime.ResponseStatusExceptionMapper;
+import io.quarkus.spring.web.runtime.ResteasyClassicResponseStatusExceptionMapper;
import io.quarkus.undertow.deployment.IgnoredServletContainerInitializerBuildItem;
import io.quarkus.undertow.deployment.ServletInitParamBuildItem;
-public class SpringWebProcessor {
+public class SpringWebResteasyClassicProcessor {
- private static final Logger LOGGER = Logger.getLogger(SpringWebProcessor.class.getName());
-
- private static final DotName REST_CONTROLLER_ANNOTATION = DotName
- .createSimple("org.springframework.web.bind.annotation.RestController");
+ private static final Logger LOGGER = Logger.getLogger(SpringWebResteasyClassicProcessor.class.getName());
private static final DotName REQUEST_MAPPING = DotName
.createSimple("org.springframework.web.bind.annotation.RequestMapping");
- private static final DotName PATH_VARIABLE = DotName.createSimple("org.springframework.web.bind.annotation.PathVariable");
-
- private static final List MAPPING_ANNOTATIONS;
-
- static {
- MAPPING_ANNOTATIONS = Arrays.asList(
- REQUEST_MAPPING,
- DotName.createSimple("org.springframework.web.bind.annotation.GetMapping"),
- DotName.createSimple("org.springframework.web.bind.annotation.PostMapping"),
- DotName.createSimple("org.springframework.web.bind.annotation.PutMapping"),
- DotName.createSimple("org.springframework.web.bind.annotation.DeleteMapping"),
- DotName.createSimple("org.springframework.web.bind.annotation.PatchMapping"));
- }
-
- private static final DotName RESPONSE_STATUS = DotName
- .createSimple("org.springframework.web.bind.annotation.ResponseStatus");
- private static final DotName EXCEPTION_HANDLER = DotName
- .createSimple("org.springframework.web.bind.annotation.ExceptionHandler");
-
- private static final DotName REST_CONTROLLER_ADVICE = DotName
- .createSimple("org.springframework.web.bind.annotation.RestControllerAdvice");
+ private static final DotName GET_MAPPING = DotName.createSimple("org.springframework.web.bind.annotation.GetMapping");
+ private static final DotName POST_MAPPING = DotName.createSimple("org.springframework.web.bind.annotation.PostMapping");
+ private static final DotName PUT_MAPPING = DotName.createSimple("org.springframework.web.bind.annotation.PutMapping");
+ private static final DotName DELETE_MAPPING = DotName.createSimple("org.springframework.web.bind.annotation.DeleteMapping");
+ private static final DotName PATCH_MAPPING = DotName.createSimple("org.springframework.web.bind.annotation.PatchMapping");
+ private static final List MAPPING_ANNOTATIONS = List.of(REQUEST_MAPPING, GET_MAPPING, POST_MAPPING,
+ PUT_MAPPING, DELETE_MAPPING, PATCH_MAPPING);
- private static final DotName MODEL_AND_VIEW = DotName.createSimple("org.springframework.web.servlet.ModelAndView");
- private static final DotName VIEW = DotName.createSimple("org.springframework.web.servlet.View");
- private static final DotName MODEL = DotName.createSimple("org.springframework.ui.Model");
-
- private static final DotName HTTP_ENTITY = DotName.createSimple("org.springframework.http.HttpEntity");
- private static final DotName RESPONSE_ENTITY = DotName.createSimple("org.springframework.http.ResponseEntity");
-
- private static final Set DISALLOWED_EXCEPTION_CONTROLLER_RETURN_TYPES = new HashSet<>(Arrays.asList(
- MODEL_AND_VIEW, VIEW, MODEL, HTTP_ENTITY));
+ private static final DotName REST_CONTROLLER_ANNOTATION = DotName
+ .createSimple("org.springframework.web.bind.annotation.RestController");
- @BuildStep
- FeatureBuildItem registerFeature() {
- return new FeatureBuildItem(Feature.SPRING_WEB);
- }
+ private static final DotName PATH_VARIABLE = DotName.createSimple("org.springframework.web.bind.annotation.PathVariable");
@BuildStep
public IgnoredServletContainerInitializerBuildItem ignoreSpringServlet() {
@@ -116,15 +77,10 @@ public AdditionalJaxRsResourceDefiningAnnotationBuildItem additionalJaxRsResourc
return new AdditionalJaxRsResourceDefiningAnnotationBuildItem(REST_CONTROLLER_ANNOTATION);
}
- @BuildStep
- public AdditionalJaxRsResourceMethodAnnotationsBuildItem additionalJaxRsResourceMethodAnnotationsBuildItem() {
- return new AdditionalJaxRsResourceMethodAnnotationsBuildItem(MAPPING_ANNOTATIONS);
- }
-
@BuildStep
public AdditionalJaxRsResourceMethodParamAnnotations additionalJaxRsResourceMethodParamAnnotations() {
return new AdditionalJaxRsResourceMethodParamAnnotations(
- Arrays.asList(DotName.createSimple("org.springframework.web.bind.annotation.RequestParam"),
+ List.of(DotName.createSimple("org.springframework.web.bind.annotation.RequestParam"),
PATH_VARIABLE,
DotName.createSimple("org.springframework.web.bind.annotation.RequestBody"),
DotName.createSimple("org.springframework.web.bind.annotation.MatrixVariable"),
@@ -133,23 +89,9 @@ public AdditionalJaxRsResourceMethodParamAnnotations additionalJaxRsResourceMeth
}
@BuildStep
- public void ignoreReflectionHierarchy(BuildProducer ignore) {
- ignore.produce(new ReflectiveHierarchyIgnoreWarningBuildItem(
- new ReflectiveHierarchyIgnoreWarningBuildItem.DotNameExclusion(RESPONSE_ENTITY)));
- ignore.produce(
- new ReflectiveHierarchyIgnoreWarningBuildItem(new ReflectiveHierarchyIgnoreWarningBuildItem.DotNameExclusion(
- DotName.createSimple("org.springframework.util.MimeType"))));
- ignore.produce(
- new ReflectiveHierarchyIgnoreWarningBuildItem(new ReflectiveHierarchyIgnoreWarningBuildItem.DotNameExclusion(
- DotName.createSimple("org.springframework.util.MultiValueMap"))));
- }
-
- @BuildStep
- public void beanDefiningAnnotations(BuildProducer beanDefiningAnnotations) {
- beanDefiningAnnotations
- .produce(new BeanDefiningAnnotationBuildItem(REST_CONTROLLER_ANNOTATION, BuiltinScope.SINGLETON.getName()));
- beanDefiningAnnotations
- .produce(new BeanDefiningAnnotationBuildItem(REST_CONTROLLER_ADVICE, BuiltinScope.SINGLETON.getName()));
+ public void registerStandardExceptionMappers(BuildProducer providersProducer) {
+ providersProducer
+ .produce(new ResteasyJaxrsProviderBuildItem(ResteasyClassicResponseStatusExceptionMapper.class.getName()));
}
@BuildStep
@@ -219,82 +161,6 @@ private void validateControllers(BeanArchiveIndexBuildItem beanArchiveIndexBuild
}
}
- @BuildStep(onlyIf = IsDevelopment.class)
- @Record(STATIC_INIT)
- public void registerWithDevModeNotFoundMapper(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
- ExceptionMapperRecorder recorder) {
- IndexView index = beanArchiveIndexBuildItem.getIndex();
- Collection restControllerAnnotations = index.getAnnotations(REST_CONTROLLER_ANNOTATION);
- if (restControllerAnnotations.isEmpty()) {
- return;
- }
-
- Map nonJaxRsPaths = new HashMap<>();
- for (AnnotationInstance restControllerInstance : restControllerAnnotations) {
- String basePath = "/";
- ClassInfo restControllerAnnotatedClass = restControllerInstance.target().asClass();
-
- AnnotationInstance requestMappingInstance = restControllerAnnotatedClass.classAnnotation(REQUEST_MAPPING);
- if (requestMappingInstance != null) {
- String basePathFromAnnotation = getMappingValue(requestMappingInstance);
- if (basePathFromAnnotation != null) {
- basePath = basePathFromAnnotation;
- }
- }
- Map methodNameToPath = new HashMap<>();
- NonJaxRsClassMappings nonJaxRsClassMappings = new NonJaxRsClassMappings();
- nonJaxRsClassMappings.setMethodNameToPath(methodNameToPath);
- nonJaxRsClassMappings.setBasePath(basePath);
-
- List methods = restControllerAnnotatedClass.methods();
-
- // go through each of the methods and see if there are any mapping Spring annotation from which to get the path
- METHOD: for (MethodInfo method : methods) {
- String methodName = method.name();
- String methodPath;
- // go through each of the annotations that can be used to make a method handle an http request
- for (DotName mappingClass : MAPPING_ANNOTATIONS) {
- AnnotationInstance mappingClassAnnotation = method.annotation(mappingClass);
- if (mappingClassAnnotation != null) {
- methodPath = getMappingValue(mappingClassAnnotation);
- if (methodPath == null) {
- methodPath = ""; // ensure that no nasty null values show up in the output
- } else if (!methodPath.startsWith("/")) {
- methodPath = "/" + methodPath;
- }
- // record the mapping of method to the http path
- methodNameToPath.put(methodName, methodPath);
- continue METHOD;
- }
- }
- }
-
- // if there was at least one controller method, add the controller since it contains methods that handle http requests
- if (!methodNameToPath.isEmpty()) {
- nonJaxRsPaths.put(restControllerAnnotatedClass.name().toString(), nonJaxRsClassMappings);
- }
- }
-
- if (!nonJaxRsPaths.isEmpty()) {
- recorder.nonJaxRsClassNameToMethodPaths(nonJaxRsPaths);
- }
- }
-
- /**
- * Meant to be called with an instance of any of the MAPPING_CLASSES
- */
- private String getMappingValue(AnnotationInstance instance) {
- if (instance == null) {
- return null;
- }
- if (instance.value() != null) {
- return instance.value().asStringArray()[0];
- } else if (instance.value("path") != null) {
- return instance.value("path").asStringArray()[0];
- }
- return null;
- }
-
@BuildStep
public void registerProviders(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
BuildProducer providersProducer) throws IOException {
@@ -366,106 +232,79 @@ private boolean collectProviders(Set providersToRegister, MediaTypeMap generatedExceptionMappers,
- BuildProducer providersProducer,
- BuildProducer reflectiveClassProducer) {
-
- TypesUtil typesUtil = new TypesUtil(Thread.currentThread().getContextClassLoader());
-
- // Look for all exception classes that are annotated with @ResponseStatus
-
+ @BuildStep(onlyIf = IsDevelopment.class)
+ @Record(STATIC_INIT)
+ public void registerWithDevModeNotFoundMapper(BeanArchiveIndexBuildItem beanArchiveIndexBuildItem,
+ ExceptionMapperRecorder recorder) {
IndexView index = beanArchiveIndexBuildItem.getIndex();
- ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedExceptionMappers, true);
- generateMappersForResponseStatusOnException(providersProducer, index, classOutput, typesUtil);
- generateMappersForExceptionHandlerInControllerAdvice(providersProducer, reflectiveClassProducer, index, classOutput,
- typesUtil);
- }
-
- @BuildStep
- public void registerStandardExceptionMappers(BuildProducer providersProducer) {
- providersProducer.produce(new ResteasyJaxrsProviderBuildItem(ResponseStatusExceptionMapper.class.getName()));
- }
-
- private void generateMappersForResponseStatusOnException(BuildProducer providersProducer,
- IndexView index, ClassOutput classOutput, TypesUtil typesUtil) {
- Collection responseStatusInstances = index
- .getAnnotations(RESPONSE_STATUS);
-
- if (responseStatusInstances.isEmpty()) {
- return;
- }
-
- for (AnnotationInstance instance : responseStatusInstances) {
- if (AnnotationTarget.Kind.CLASS != instance.target().kind()) {
- continue;
- }
- if (!typesUtil.isAssignable(Exception.class, instance.target().asClass().name())) {
- continue;
- }
-
- String name = new ResponseStatusOnExceptionGenerator(instance.target().asClass(), classOutput).generate();
- providersProducer.produce(new ResteasyJaxrsProviderBuildItem(name));
- }
- }
-
- private void generateMappersForExceptionHandlerInControllerAdvice(
- BuildProducer providersProducer,
- BuildProducer reflectiveClassProducer, IndexView index, ClassOutput classOutput,
- TypesUtil typesUtil) {
-
- AnnotationInstance controllerAdviceInstance = getSingleControllerAdviceInstance(index);
- if (controllerAdviceInstance == null) {
+ Collection restControllerAnnotations = index.getAnnotations(REST_CONTROLLER_ANNOTATION);
+ if (restControllerAnnotations.isEmpty()) {
return;
}
- ClassInfo controllerAdvice = controllerAdviceInstance.target().asClass();
- List methods = controllerAdvice.methods();
- for (MethodInfo method : methods) {
- AnnotationInstance exceptionHandlerInstance = method.annotation(EXCEPTION_HANDLER);
- if (exceptionHandlerInstance == null) {
- continue;
- }
+ Map nonJaxRsPaths = new HashMap<>();
+ for (AnnotationInstance restControllerInstance : restControllerAnnotations) {
+ String basePath = "/";
+ ClassInfo restControllerAnnotatedClass = restControllerInstance.target().asClass();
- if (!Modifier.isPublic(method.flags()) || Modifier.isStatic(method.flags())) {
- throw new IllegalStateException(
- "@ExceptionHandler methods in @ControllerAdvice must be public instance methods");
+ AnnotationInstance requestMappingInstance = restControllerAnnotatedClass.classAnnotation(REQUEST_MAPPING);
+ if (requestMappingInstance != null) {
+ String basePathFromAnnotation = getMappingValue(requestMappingInstance);
+ if (basePathFromAnnotation != null) {
+ basePath = basePathFromAnnotation;
+ }
}
+ Map methodNameToPath = new HashMap<>();
+ NonJaxRsClassMappings nonJaxRsClassMappings = new NonJaxRsClassMappings();
+ nonJaxRsClassMappings.setMethodNameToPath(methodNameToPath);
+ nonJaxRsClassMappings.setBasePath(basePath);
- DotName returnTypeDotName = method.returnType().name();
- if (DISALLOWED_EXCEPTION_CONTROLLER_RETURN_TYPES.contains(returnTypeDotName)) {
- throw new IllegalStateException(
- "@ExceptionHandler methods in @ControllerAdvice classes can only have void, ResponseEntity or POJO return types");
- }
+ List methods = restControllerAnnotatedClass.methods();
- if (!RESPONSE_ENTITY.equals(returnTypeDotName)) {
- reflectiveClassProducer.produce(new ReflectiveClassBuildItem(true, true, returnTypeDotName.toString()));
+ // go through each of the methods and see if there are any mapping Spring annotation from which to get the path
+ METHOD: for (MethodInfo method : methods) {
+ String methodName = method.name();
+ String methodPath;
+ // go through each of the annotations that can be used to make a method handle an http request
+ for (DotName mappingClass : MAPPING_ANNOTATIONS) {
+ AnnotationInstance mappingClassAnnotation = method.annotation(mappingClass);
+ if (mappingClassAnnotation != null) {
+ methodPath = getMappingValue(mappingClassAnnotation);
+ if (methodPath == null) {
+ methodPath = ""; // ensure that no nasty null values show up in the output
+ } else if (!methodPath.startsWith("/")) {
+ methodPath = "/" + methodPath;
+ }
+ // record the mapping of method to the http path
+ methodNameToPath.put(methodName, methodPath);
+ continue METHOD;
+ }
+ }
}
- // we need to generate one JAX-RS ExceptionMapper per Exception type
- Type[] handledExceptionTypes = exceptionHandlerInstance.value().asClassArray();
- for (Type handledExceptionType : handledExceptionTypes) {
- String name = new ControllerAdviceAbstractExceptionMapperGenerator(method, handledExceptionType.name(),
- classOutput, typesUtil).generate();
- providersProducer.produce(new ResteasyJaxrsProviderBuildItem(name));
+ // if there was at least one controller method, add the controller since it contains methods that handle http requests
+ if (!methodNameToPath.isEmpty()) {
+ nonJaxRsPaths.put(restControllerAnnotatedClass.name().toString(), nonJaxRsClassMappings);
}
+ }
+ if (!nonJaxRsPaths.isEmpty()) {
+ recorder.nonJaxRsClassNameToMethodPaths(nonJaxRsPaths);
}
}
- private AnnotationInstance getSingleControllerAdviceInstance(IndexView index) {
- Collection controllerAdviceInstances = index.getAnnotations(REST_CONTROLLER_ADVICE);
-
- if (controllerAdviceInstances.isEmpty()) {
+ /**
+ * Meant to be called with an instance of any of the MAPPING_CLASSES
+ */
+ private String getMappingValue(AnnotationInstance instance) {
+ if (instance == null) {
return null;
}
-
- if (controllerAdviceInstances.size() > 1) {
- throw new IllegalStateException("You can only have a single class annotated with @ControllerAdvice");
+ if (instance.value() != null) {
+ return instance.value().asStringArray()[0];
+ } else if (instance.value("path") != null) {
+ return instance.value("path").asStringArray()[0];
}
-
- return controllerAdviceInstances.iterator().next();
+ return null;
}
-
}
diff --git a/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/MissingRestControllerTest.java b/extensions/spring-web/resteasy-classic/deployment/src/test/java/io/quarkus/spring/web/test/MissingRestControllerTest.java
similarity index 92%
rename from extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/MissingRestControllerTest.java
rename to extensions/spring-web/resteasy-classic/deployment/src/test/java/io/quarkus/spring/web/test/MissingRestControllerTest.java
index 3ad617d1200a8..4d2f69ec83189 100644
--- a/extensions/spring-web/deployment/src/test/java/io/quarkus/spring/web/test/MissingRestControllerTest.java
+++ b/extensions/spring-web/resteasy-classic/deployment/src/test/java/io/quarkus/spring/web/test/MissingRestControllerTest.java
@@ -25,7 +25,8 @@ public class MissingRestControllerTest {
.addClasses(NonAnnotatedController.class, ProperController.class))
.setApplicationName("missing-rest-controller")
.setApplicationVersion("0.1-SNAPSHOT")
- .setLogRecordPredicate(r -> "io.quarkus.spring.web.deployment.SpringWebProcessor".equals(r.getLoggerName()));
+ .setLogRecordPredicate(
+ r -> "io.quarkus.spring.web.deployment.SpringWebResteasyClassicProcessor".equals(r.getLoggerName()));
@ProdBuildResults
private ProdModeTestResults prodModeTestResults;
diff --git a/extensions/spring-web/resteasy-classic/pom.xml b/extensions/spring-web/resteasy-classic/pom.xml
new file mode 100644
index 0000000000000..8141e8376f124
--- /dev/null
+++ b/extensions/spring-web/resteasy-classic/pom.xml
@@ -0,0 +1,21 @@
+
+
+
+ quarkus-spring-web-parent-aggregator
+ io.quarkus
+ 999-SNAPSHOT
+ ../pom.xml
+
+ 4.0.0
+
+ quarkus-spring-web-resteasy-classic-parent
+ Quarkus - Spring Web - RESTEasy Classic - Parent
+ pom
+
+ deployment
+ runtime
+
+
+
diff --git a/extensions/spring-web/resteasy-classic/runtime/pom.xml b/extensions/spring-web/resteasy-classic/runtime/pom.xml
new file mode 100644
index 0000000000000..e5d8688143837
--- /dev/null
+++ b/extensions/spring-web/resteasy-classic/runtime/pom.xml
@@ -0,0 +1,62 @@
+
+
+
+ quarkus-spring-web-resteasy-classic-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+
+ 4.0.0
+
+ quarkus-spring-web-resteasy-classic
+ Quarkus - Spring Web - RESTEasy Classic - Runtime
+
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ org.jboss.resteasy
+ resteasy-spring-web
+
+
+ javax.enterprise
+ cdi-api
+
+
+ jakarta.activation
+ jakarta.activation-api
+
+
+
+
+ io.quarkus
+ quarkus-spring-web-common
+
+
+
+
+
+
+ io.quarkus
+ quarkus-bootstrap-maven-plugin
+
+
+ process-resources
+
+ extension-descriptor
+
+
+
+ io.quarkus:quarkus-resteasy-jackson
+
+
+
+
+
+
+
+
diff --git a/extensions/spring-web/resteasy-classic/runtime/src/main/java/io/quarkus/spring/web/runtime/ResteasyClassicResponseContentTypeResolver.java b/extensions/spring-web/resteasy-classic/runtime/src/main/java/io/quarkus/spring/web/runtime/ResteasyClassicResponseContentTypeResolver.java
new file mode 100644
index 0000000000000..298282d10b046
--- /dev/null
+++ b/extensions/spring-web/resteasy-classic/runtime/src/main/java/io/quarkus/spring/web/runtime/ResteasyClassicResponseContentTypeResolver.java
@@ -0,0 +1,21 @@
+package io.quarkus.spring.web.runtime;
+
+import java.util.List;
+
+import javax.ws.rs.core.Variant;
+
+import org.jboss.resteasy.core.request.ServerDrivenNegotiation;
+
+import io.quarkus.spring.web.runtime.common.AbstractResponseContentTypeResolver;
+
+@SuppressWarnings("unused")
+public class ResteasyClassicResponseContentTypeResolver extends AbstractResponseContentTypeResolver {
+
+ @Override
+ protected Variant negotiateBestMatch(List acceptHeaders, List variants) {
+ ServerDrivenNegotiation negotiation = new ServerDrivenNegotiation();
+ negotiation.setAcceptHeaders(acceptHeaders);
+
+ return negotiation.getBestMatch(variants);
+ }
+}
diff --git a/extensions/spring-web/resteasy-classic/runtime/src/main/java/io/quarkus/spring/web/runtime/ResteasyClassicResponseStatusExceptionMapper.java b/extensions/spring-web/resteasy-classic/runtime/src/main/java/io/quarkus/spring/web/runtime/ResteasyClassicResponseStatusExceptionMapper.java
new file mode 100644
index 0000000000000..e0f9c53f6c715
--- /dev/null
+++ b/extensions/spring-web/resteasy-classic/runtime/src/main/java/io/quarkus/spring/web/runtime/ResteasyClassicResponseStatusExceptionMapper.java
@@ -0,0 +1,16 @@
+package io.quarkus.spring.web.runtime;
+
+import javax.ws.rs.core.Response;
+
+import org.jboss.resteasy.specimpl.ResponseBuilderImpl;
+import org.springframework.web.server.ResponseStatusException;
+
+import io.quarkus.spring.web.runtime.common.AbstractResponseStatusExceptionMapper;
+
+public class ResteasyClassicResponseStatusExceptionMapper extends AbstractResponseStatusExceptionMapper {
+
+ @Override
+ protected Response.ResponseBuilder createResponseBuilder(ResponseStatusException exception) {
+ return new ResponseBuilderImpl().status(exception.getStatus().value());
+ }
+}
diff --git a/extensions/spring-web/resteasy-classic/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/spring-web/resteasy-classic/runtime/src/main/resources/META-INF/quarkus-extension.yaml
new file mode 100644
index 0000000000000..8524de86240e9
--- /dev/null
+++ b/extensions/spring-web/resteasy-classic/runtime/src/main/resources/META-INF/quarkus-extension.yaml
@@ -0,0 +1,5 @@
+---
+artifact: ${project.groupId}:${project.artifactId}:${project.version}
+name: "Spring Web RESTEasy Classic"
+metadata:
+ unlisted: true
diff --git a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java b/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java
deleted file mode 100644
index 13e6e0f6baf4c..0000000000000
--- a/extensions/spring-web/runtime/src/main/java/io/quarkus/spring/web/runtime/ResponseEntityConverter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package io.quarkus.spring.web.runtime;
-
-import java.lang.annotation.Annotation;
-import java.util.List;
-import java.util.Map;
-
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
-import org.jboss.resteasy.core.Headers;
-import org.jboss.resteasy.specimpl.BuiltResponse;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.ResponseEntity;
-
-/**
- * This is only used in the generated ExceptionMappers when the Spring @RestControllerAdvice method returns a ResponseEntity
- */
-public class ResponseEntityConverter {
-
- public static Response toResponse(ResponseEntity responseEntity, MediaType defaultContentType) {
- return new BuiltResponse(responseEntity.getStatusCodeValue(),
- addContentTypeIfMissing(toJaxRsHeaders(responseEntity.getHeaders()), defaultContentType),
- responseEntity.getBody(),
- new Annotation[0]);
- }
-
- private static Headers
+
+ io.quarkus
+ quarkus-resteasy-jackson
+
io.quarkus
quarkus-hibernate-validator
@@ -99,6 +103,19 @@
+
+ io.quarkus
+ quarkus-resteasy-jackson-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
io.quarkus
quarkus-resteasy-jaxb-deployment