Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple quarkus-spring-web from RESTEasy Classic #21082

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Decouple quarkus-spring-web from RESTEasy Classic
This is prerequisite work for being able to run
Spring Web endpoints on RESTEasy Reactive in addition
to RESTEasy Classic.

The decoupling is achieved by using conditional
dependencies and moving RESTEasy specific code
to a new extension
geoand committed Oct 29, 2021

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 24eda12307ac190c5b31a36971ad35f1973ab2e5
15 changes: 15 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
@@ -1788,6 +1788,11 @@
<artifactId>quarkus-spring-scheduled-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web</artifactId>
@@ -1798,6 +1803,16 @@
<artifactId>quarkus-spring-web-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-classic</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-classic-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-jpa</artifactId>
13 changes: 13 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
@@ -2671,6 +2671,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-classic</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-swagger-ui</artifactId>
13 changes: 13 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
@@ -2632,6 +2632,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-classic-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-swagger-ui-deployment</artifactId>
42 changes: 42 additions & 0 deletions extensions/spring-web/core/common-runtime/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-spring-web-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web-common</artifactId>
<name>Quarkus - Spring Web - Common Runtime</name>

<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.1_spec</artifactId>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-webmvc-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-core-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-context-api</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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<String> acceptHeaders, List<Variant> 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<String> acceptHeaders, List<Variant> variants) {
private Variant getBestVariant(List<String> acceptHeaders, List<Variant> 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<Variant> getMediaTypeVariants(String... mediaTypes) {
Original file line number Diff line number Diff line change
@@ -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<ResponseStatusException> {
public abstract class AbstractResponseStatusExceptionMapper implements ExceptionMapper<ResponseStatusException> {

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();
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web-deployment</artifactId>
<name>Quarkus - Spring - Web - Deployment</name>
<name>Quarkus - Spring Web - Deployment</name>

<dependencies>
<dependency>
@@ -24,15 +24,17 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
<artifactId>quarkus-spring-web-resteasy-classic-deployment</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaxrs-spi-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-common-spi</artifactId>
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
Original file line number Diff line number Diff line change
@@ -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<St
.map(methodCreator::load)
.toArray(ResultHandle[]::new);

return methodCreator.invokeStaticMethod(
MethodDescriptor.ofMethod(ResponseContentTypeResolver.class, "resolve", MediaType.class,
String responseContentTypeResolverClassName = isResteasyClassic
? "io.quarkus.spring.web.runtime.ResteasyClassicResponseContentTypeResolver"
: "io.quarkus.spring.web.runtime.ResteasyReactiveResponseContentTypeResolver"; // doesn't exist yet
ResultHandle contentTypeResolver = methodCreator
.newInstance(MethodDescriptor.ofConstructor(responseContentTypeResolverClassName));
return methodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(responseContentTypeResolverClassName, "resolve", MediaType.class,
HttpHeaders.class, String[].class),
contentTypeResolver,
methodCreator.readInstanceField(httpHeadersField, methodCreator.getThis()),
methodCreator.marshalAsArray(String.class, supportedMediaTypes));
}
Original file line number Diff line number Diff line change
@@ -12,10 +12,12 @@
class ResponseStatusOnExceptionGenerator extends AbstractExceptionMapperGenerator {

private final ClassInfo exceptionClassInfo;
private final boolean isResteasyClassic;

ResponseStatusOnExceptionGenerator(ClassInfo exceptionClassInfo, ClassOutput classOutput) {
ResponseStatusOnExceptionGenerator(ClassInfo exceptionClassInfo, ClassOutput classOutput, boolean isResteasyClassic) {
super(exceptionClassInfo.name(), classOutput);
this.exceptionClassInfo = exceptionClassInfo;
this.isResteasyClassic = isResteasyClassic;
}

void generateMethodBody(MethodCreator toResponse) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package io.quarkus.spring.web.deployment;

import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;

import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
import io.quarkus.arc.deployment.BeanDefiningAnnotationBuildItem;
import io.quarkus.arc.processor.BuiltinScope;
import io.quarkus.deployment.Capabilities;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.GeneratedClassGizmoAdaptor;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
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.gizmo.ClassOutput;
import io.quarkus.jaxrs.spi.deployment.AdditionalJaxRsResourceMethodAnnotationsBuildItem;
import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem;

public class SpringWebProcessor {

private static final DotName REST_CONTROLLER_ANNOTATION = DotName
.createSimple("org.springframework.web.bind.annotation.RestController");

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<DotName> 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<DotName> 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<ReflectiveHierarchyIgnoreWarningBuildItem> 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<BeanDefiningAnnotationBuildItem> 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<GeneratedClassBuildItem> generatedExceptionMappers,
BuildProducer<ResteasyJaxrsProviderBuildItem> providersProducer,
BuildProducer<ReflectiveClassBuildItem> 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<ResteasyJaxrsProviderBuildItem> providersProducer,
IndexView index, ClassOutput classOutput, TypesUtil typesUtil, boolean isResteasyClassic) {
Collection<AnnotationInstance> 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<ResteasyJaxrsProviderBuildItem> providersProducer,
BuildProducer<ReflectiveClassBuildItem> reflectiveClassProducer, IndexView index, ClassOutput classOutput,
TypesUtil typesUtil, boolean isResteasyClassic) {

AnnotationInstance controllerAdviceInstance = getSingleControllerAdviceInstance(index);
if (controllerAdviceInstance == null) {
return;
}

ClassInfo controllerAdvice = controllerAdviceInstance.target().asClass();
List<MethodInfo> 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<AnnotationInstance> 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();
}

}
23 changes: 23 additions & 0 deletions extensions/spring-web/core/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-spring-web-parent-aggregator</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web-parent</artifactId>
<name>Quarkus - Spring Web</name>
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>runtime</module>
<module>common-runtime</module>
</modules>


</project>
Original file line number Diff line number Diff line change
@@ -11,51 +11,18 @@
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web</artifactId>
<name>Quarkus - Spring - Web - Runtime</name>
<name>Quarkus - Spring Web - Runtime</name>
<description>Use Spring Web annotations to create your REST services</description>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring-web</artifactId>
<exclusions>
<exclusion>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
<artifactId>quarkus-spring-web-resteasy-classic</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-di</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-webmvc-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-core-api</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-context-api</artifactId>
</dependency>
</dependencies>

<build>
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> toJaxRsHeaders(HttpHeaders springHeaders) {
var jaxRsHeaders = new MultivaluedTreeMap<String, String>();
for (var entry : springHeaders.entrySet()) {
jaxRsHeaders.addAll(entry.getKey(), entry.getValue().toArray(EMPTY_STRINGS_ARRAY));
}
return jaxRsHeaders;
}
}
8 changes: 4 additions & 4 deletions extensions/spring-web/pom.xml
Original file line number Diff line number Diff line change
@@ -10,11 +10,11 @@
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web-parent</artifactId>
<name>Quarkus - Spring - Web</name>
<artifactId>quarkus-spring-web-parent-aggregator</artifactId>
<name>Quarkus - Spring Web - Parent - Aggregator</name>
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>runtime</module>
<module>core</module>
<module>resteasy-classic</module>
</modules>
</project>
69 changes: 69 additions & 0 deletions extensions/spring-web/resteasy-classic/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-spring-web-resteasy-classic-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web-resteasy-classic-deployment</artifactId>
<name>Quarkus - Spring Web - RESTEasy Classic - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-common-spi</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-classic</artifactId>
</dependency>

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-api</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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;
21 changes: 21 additions & 0 deletions extensions/spring-web/resteasy-classic/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-spring-web-parent-aggregator</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web-resteasy-classic-parent</artifactId>
<name>Quarkus - Spring Web - RESTEasy Classic - Parent</name>
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>runtime</module>
</modules>

</project>
62 changes: 62 additions & 0 deletions extensions/spring-web/resteasy-classic/runtime/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-spring-web-resteasy-classic-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-spring-web-resteasy-classic</artifactId>
<name>Quarkus - Spring Web - RESTEasy Classic - Runtime</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-spring-web</artifactId>
<exclusions>
<exclusion>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
</exclusion>
<exclusion>
<groupId>jakarta.activation</groupId>
<artifactId>jakarta.activation-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-common</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-resources</phase>
<goals>
<goal>extension-descriptor</goal>
</goals>
<configuration>
<dependencyCondition>
<artifact>io.quarkus:quarkus-resteasy-jackson</artifact>
</dependencyCondition>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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<String> acceptHeaders, List<Variant> variants) {
ServerDrivenNegotiation negotiation = new ServerDrivenNegotiation();
negotiation.setAcceptHeaders(acceptHeaders);

return negotiation.getBestMatch(variants);
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
artifact: ${project.groupId}:${project.artifactId}:${project.version}
name: "Spring Web RESTEasy Classic"
metadata:
unlisted: true

This file was deleted.

17 changes: 17 additions & 0 deletions integration-tests/spring-web/pom.xml
Original file line number Diff line number Diff line change
@@ -38,6 +38,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jaxb</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-validator</artifactId>
@@ -99,6 +103,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-jaxb-deployment</artifactId>