Skip to content

Commit

Permalink
Allow spring-web to work on RESTEasy Reactive
Browse files Browse the repository at this point in the history
Resolves: quarkusio#19819
  • Loading branch information
geoand committed Nov 1, 2021
1 parent 4c94b85 commit c677eeb
Show file tree
Hide file tree
Showing 28 changed files with 1,409 additions and 49 deletions.
10 changes: 10 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1813,6 +1813,16 @@
<artifactId>quarkus-spring-web-resteasy-classic-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-reactive</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-reactive-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-data-jpa</artifactId>
Expand Down
13 changes: 13 additions & 0 deletions devtools/bom-descriptor-json/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2684,6 +2684,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-reactive</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>
Expand Down
13 changes: 13 additions & 0 deletions docs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2645,6 +2645,19 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-reactive-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>
Expand Down
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.Collections;
import java.util.HashMap;
Expand All @@ -21,7 +21,7 @@ public static Response toResponse(ResponseEntity responseEntity, MediaType defau
Response.ResponseBuilder responseBuilder = Response.status(responseEntity.getStatusCodeValue())
.entity(responseEntity.getBody());
var jaxRsHeaders = toJaxRsHeaders(responseEntity.getHeaders());
if (!jaxRsHeaders.containsKey(HttpHeaders.CONTENT_TYPE)) {
if (!jaxRsHeaders.containsKey(HttpHeaders.CONTENT_TYPE) && (defaultMediaType != null)) {
jaxRsHeaders.put(HttpHeaders.CONTENT_TYPE, Collections.singletonList(defaultMediaType.toString()));
}
for (var entry : jaxRsHeaders.entrySet()) {
Expand Down
9 changes: 9 additions & 0 deletions extensions/spring-web/core/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,19 @@
<artifactId>quarkus-spring-web-resteasy-classic-deployment</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-spring-web-resteasy-reactive-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-reactive-spi-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-common-spi</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,19 @@ abstract class AbstractExceptionMapperGenerator {
protected final DotName exceptionDotName;
protected final ClassOutput classOutput;

AbstractExceptionMapperGenerator(DotName exceptionDotName, ClassOutput classOutput) {
private final boolean isResteasyClassic;

AbstractExceptionMapperGenerator(DotName exceptionDotName, ClassOutput classOutput, boolean isResteasyClassic) {
this.exceptionDotName = exceptionDotName;
this.classOutput = classOutput;
this.isResteasyClassic = isResteasyClassic;
}

abstract void generateMethodBody(MethodCreator toResponse);

String generate() {
String generatedClassName = "io.quarkus.spring.web.mappers." + exceptionDotName.withoutPackagePrefix() + "_Mapper_"
+ HashUtil.sha1(exceptionDotName.toString());
String generatedSubtypeClassName = "io.quarkus.spring.web.mappers.Subtype" + exceptionDotName.withoutPackagePrefix()
+ "Mapper_" + HashUtil.sha1(exceptionDotName.toString());
String exceptionClassName = exceptionDotName.toString();

try (ClassCreator cc = ClassCreator.builder()
Expand All @@ -64,15 +65,20 @@ String generate() {
}
}

// additionally generate a dummy subtype to get past the RESTEasy's ExceptionMapper check for synthetic classes
try (ClassCreator cc = ClassCreator.builder()
.classOutput(classOutput).className(generatedSubtypeClassName)
.superClass(generatedClassName)
.build()) {
cc.addAnnotation(Provider.class);
}
if (isResteasyClassic) {
String generatedSubtypeClassName = "io.quarkus.spring.web.mappers.Subtype" + exceptionDotName.withoutPackagePrefix()
+ "Mapper_" + HashUtil.sha1(exceptionDotName.toString());
// additionally generate a dummy subtype to get past the RESTEasy's ExceptionMapper check for synthetic classes
try (ClassCreator cc = ClassCreator.builder()
.classOutput(classOutput).className(generatedSubtypeClassName)
.superClass(generatedClassName)
.build()) {
cc.addAnnotation(Provider.class);
}

return generatedSubtypeClassName;
return generatedSubtypeClassName;
}
return generatedClassName;
}

protected void preGenerateMethodBody(ClassCreator cc) {
Expand All @@ -93,6 +99,7 @@ protected int getHttpStatusFromAnnotation(AnnotationInstance responseStatusInsta
return 500; // the default value of @ResponseStatus
}

@SuppressWarnings({ "rawtypes", "unchecked" })
private int enumValueToHttpStatus(String enumValue) {
try {
Class<?> httpStatusClass = Class.forName("org.springframework.http.HttpStatus");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.DotName;
Expand All @@ -29,9 +31,9 @@
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.spring.web.runtime.ResponseEntityConverter;
import io.quarkus.spring.web.runtime.common.ResponseEntityConverter;

class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractExceptionMapperGenerator {
class ControllerAdviceExceptionMapperGenerator extends AbstractExceptionMapperGenerator {

private static final DotName RESPONSE_ENTITY = DotName.createSimple("org.springframework.http.ResponseEntity");

Expand All @@ -54,14 +56,9 @@ class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractException

private final boolean isResteasyClassic;

ControllerAdviceAbstractExceptionMapperGenerator(MethodInfo controllerAdviceMethod, DotName exceptionDotName,
ControllerAdviceExceptionMapperGenerator(MethodInfo controllerAdviceMethod, DotName exceptionDotName,
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");
}
super(exceptionDotName, classOutput, isResteasyClassic);

this.controllerAdviceMethod = controllerAdviceMethod;
this.typesUtil = typesUtil;
Expand All @@ -80,6 +77,10 @@ class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractException
*/
@Override
protected void preGenerateMethodBody(ClassCreator cc) {
if (!isResteasyClassic) {
return;
}

int notAllowedParameterIndex = -1;
for (int i = 0; i < parameterTypes.size(); i++) {
Type parameterType = parameterTypes.get(i);
Expand Down Expand Up @@ -197,14 +198,23 @@ private ResultHandle getResponseContentType(MethodCreator methodCreator, List<St

String responseContentTypeResolverClassName = isResteasyClassic
? "io.quarkus.spring.web.runtime.ResteasyClassicResponseContentTypeResolver"
: "io.quarkus.spring.web.runtime.ResteasyReactiveResponseContentTypeResolver"; // doesn't exist yet
: "io.quarkus.spring.web.runtime.ResteasyReactiveResponseContentTypeResolver";
ResultHandle contentTypeResolver = methodCreator
.newInstance(MethodDescriptor.ofConstructor(responseContentTypeResolverClassName));

if (isResteasyClassic) {
return methodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(responseContentTypeResolverClassName, "resolve", MediaType.class,
HttpHeaders.class, String[].class),
contentTypeResolver,
methodCreator.readInstanceField(httpHeadersField, methodCreator.getThis()),
methodCreator.marshalAsArray(String.class, supportedMediaTypes));
}
return methodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(responseContentTypeResolverClassName, "resolve", MediaType.class,
HttpHeaders.class, String[].class),
contentTypeResolver,
methodCreator.readInstanceField(httpHeadersField, methodCreator.getThis()),
getBeanFromArc(methodCreator, HttpHeaders.class.getName()),
methodCreator.marshalAsArray(String.class, supportedMediaTypes));
}

Expand All @@ -219,14 +229,34 @@ private ResultHandle invokeExceptionHandlerMethod(MethodCreator toResponse) {

String[] parameterTypesStr = new String[parameterTypes.size()];
ResultHandle[] parameterTypeHandles = new ResultHandle[parameterTypes.size()];
for (int i = 0; i < parameterTypes.size(); i++) {
Type parameterType = parameterTypes.get(i);
parameterTypesStr[i] = parameterType.name().toString();
if (typesUtil.isAssignable(Exception.class, parameterType.name())) {
parameterTypeHandles[i] = toResponse.getMethodParam(i);
} else {
parameterTypeHandles[i] = toResponse.readInstanceField(parameterTypeToField.get(parameterType),
toResponse.getThis());
if (isResteasyClassic) {
for (int i = 0; i < parameterTypes.size(); i++) {
Type parameterType = parameterTypes.get(i);
parameterTypesStr[i] = parameterType.name().toString();
if (typesUtil.isAssignable(Exception.class, parameterType.name())) {
parameterTypeHandles[i] = toResponse.getMethodParam(i);
} else {
parameterTypeHandles[i] = toResponse.readInstanceField(parameterTypeToField.get(parameterType),
toResponse.getThis());
}
}
} else {
for (int i = 0; i < parameterTypes.size(); i++) {
Type parameterType = parameterTypes.get(i);
parameterTypesStr[i] = parameterType.name().toString();
if (typesUtil.isAssignable(Exception.class, parameterType.name())) {
parameterTypeHandles[i] = toResponse.getMethodParam(i);
} else if (typesUtil.isAssignable(UriInfo.class, parameterType.name())) {
parameterTypeHandles[i] = getBeanFromArc(toResponse, UriInfo.class.getName());
} else if (typesUtil.isAssignable(Request.class, parameterType.name())) {
parameterTypeHandles[i] = getBeanFromArc(toResponse, Request.class.getName());
} else {
throw new IllegalArgumentException(
"Parameter type '" + parameterType.name() + "' is not supported for method '"
+ controllerAdviceMethod.name() + "' of class '"
+ controllerAdviceMethod.declaringClass().name()
+ "'");
}
}
}

Expand All @@ -237,18 +267,35 @@ private ResultHandle invokeExceptionHandlerMethod(MethodCreator toResponse) {
}

private ResultHandle controllerAdviceInstance(MethodCreator toResponse) {
ResultHandle controllerAdviceClass = toResponse.loadClass(declaringClassName);
if (isResteasyClassic) {
ResultHandle controllerAdviceClass = toResponse.loadClass(declaringClassName);

ResultHandle container = toResponse
.invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class));
ResultHandle instance = toResponse.invokeInterfaceMethod(
MethodDescriptor.ofMethod(ArcContainer.class, "instance", InstanceHandle.class, Class.class,
Annotation[].class),
container, controllerAdviceClass, toResponse.loadNull());
ResultHandle bean = toResponse.invokeInterfaceMethod(
MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class),
instance);
return toResponse.checkCast(bean, controllerAdviceMethod.declaringClass().name().toString());
} else {
return toResponse.checkCast(getBeanFromArc(toResponse, declaringClassName),
controllerAdviceMethod.declaringClass().name().toString());
}
}

ResultHandle container = toResponse
private ResultHandle getBeanFromArc(MethodCreator methodCreator, String beanClassName) {
ResultHandle container = methodCreator
.invokeStaticMethod(MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class));
ResultHandle instance = toResponse.invokeInterfaceMethod(
ResultHandle instance = methodCreator.invokeInterfaceMethod(
MethodDescriptor.ofMethod(ArcContainer.class, "instance", InstanceHandle.class, Class.class,
Annotation[].class),
container, controllerAdviceClass, toResponse.loadNull());
ResultHandle bean = toResponse.invokeInterfaceMethod(
container, methodCreator.loadClass(beanClassName), methodCreator.loadNull());
return methodCreator.invokeInterfaceMethod(
MethodDescriptor.ofMethod(InstanceHandle.class, "get", Object.class),
instance);
return toResponse.checkCast(bean, controllerAdviceMethod.declaringClass().name().toString());
}

private int getAnnotationStatusOrDefault(int defaultValue) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@
class ResponseStatusOnExceptionGenerator extends AbstractExceptionMapperGenerator {

private final ClassInfo exceptionClassInfo;
private final boolean isResteasyClassic;

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

void generateMethodBody(MethodCreator toResponse) {
Expand Down
Loading

0 comments on commit c677eeb

Please sign in to comment.