Skip to content

Commit

Permalink
Merge pull request #20400 from geoand/spring-web-rr-revert
Browse files Browse the repository at this point in the history
Revert "Port Spring Web to RESTEasy Reactive"
  • Loading branch information
geoand authored Sep 27, 2021
2 parents a372641 + 6f13404 commit 38525a7
Show file tree
Hide file tree
Showing 26 changed files with 560 additions and 958 deletions.
8 changes: 7 additions & 1 deletion docs/src/main/asciidoc/spring-web.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@ The following method return types are supported:
* POJO classes which will be serialized via JSON
* `org.springframework.http.ResponseEntity`

=== Controller method parameter types

In addition to the method parameters that can be annotated with the appropriate Spring Web annotations from the previous table,
`javax.servlet.http.HttpServletRequest` and `javax.servlet.http.HttpServletResponse` are also supported.
For this to function however, users need to add the `quarkus-undertow` dependency.

=== Exception handler method return types

The following method return types are supported:
Expand All @@ -443,7 +449,7 @@ Other return types mentioned in the Spring `https://docs.spring.io/spring-framew
The following parameter types are supported, in arbitrary order:

* An exception argument: declared as a general `Exception` or as a more specific exception. This also serves as a mapping hint if the annotation itself does not narrow the exception types through its `value()`.
* The following JAX-RS Types: `javax.ws.rs.core.Request` and `javax.ws.rs.core.UriInfo`
* Request and/or response objects (typically from the Servlet API). You may choose any specific request/response type, e.g. `ServletRequest` / `HttpServletRequest`. To use Servlet API, the `quarkus-undertow` dependency needs to be added.

Other parameter types mentioned in the Spring `https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ExceptionHandler.html[ExceptionHandler javadoc]` are not supported.

Expand Down
10 changes: 9 additions & 1 deletion extensions/spring-web/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson-deployment</artifactId>
<artifactId>quarkus-resteasy-jackson-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaxrs-spi-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-common-spi</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;

import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationValue;
Expand Down Expand Up @@ -35,6 +36,8 @@ abstract class AbstractExceptionMapperGenerator {
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 @@ -61,7 +64,15 @@ String generate() {
}
}

return generatedClassName;
// 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;
}

protected void preGenerateMethodBody(ClassCreator cc) {
Expand All @@ -82,7 +93,6 @@ 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
@@ -1,14 +1,18 @@
package io.quarkus.spring.web.deployment;

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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 @@ -18,31 +22,38 @@
import io.quarkus.arc.Arc;
import io.quarkus.arc.ArcContainer;
import io.quarkus.arc.InstanceHandle;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.FieldCreator;
import io.quarkus.gizmo.FieldDescriptor;
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 ControllerAdviceExceptionMapperGenerator extends AbstractExceptionMapperGenerator {
class ControllerAdviceAbstractExceptionMapperGenerator extends AbstractExceptionMapperGenerator {

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

// Preferred content types order for String or primitive type responses
private static final List<String> TEXT_MEDIA_TYPES = Arrays.asList(
MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON);
MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML);
// Preferred content types order for object type responses
private static final List<String> OBJECT_MEDIA_TYPES = Arrays.asList(
MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN);
MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_XML, MediaType.TEXT_PLAIN);

private final MethodInfo controllerAdviceMethod;
private final TypesUtil typesUtil;
private final Type returnType;
private final List<Type> parameterTypes;
private final String declaringClassName;

ControllerAdviceExceptionMapperGenerator(MethodInfo controllerAdviceMethod, DotName exceptionDotName,
private final Map<Type, FieldDescriptor> parameterTypeToField = new HashMap<>();

private FieldDescriptor httpHeadersField;

ControllerAdviceAbstractExceptionMapperGenerator(MethodInfo controllerAdviceMethod, DotName exceptionDotName,
ClassOutput classOutput, TypesUtil typesUtil) {
super(exceptionDotName, classOutput);
this.controllerAdviceMethod = controllerAdviceMethod;
Expand All @@ -53,6 +64,69 @@ class ControllerAdviceExceptionMapperGenerator extends AbstractExceptionMapperGe
this.declaringClassName = controllerAdviceMethod.declaringClass().name().toString();
}

/**
* We need to go through each parameter of the method of the ControllerAdvice
* and make sure it's supported
* The javax.ws.rs.ext.ExceptionMapper only has one parameter, the exception, however
* other parameters can be obtained using @Context and therefore injected into the target method
*/
@Override
protected void preGenerateMethodBody(ClassCreator cc) {
int notAllowedParameterIndex = -1;
for (int i = 0; i < parameterTypes.size(); i++) {
Type parameterType = parameterTypes.get(i);
DotName parameterTypeDotName = parameterType.name();
if (typesUtil.isAssignable(Exception.class, parameterTypeDotName)) {
// do nothing since this will be handled during in generateMethodBody
} else if (typesUtil.isAssignable(HttpServletRequest.class, parameterTypeDotName)) {
if (parameterTypeToField.containsKey(parameterType)) {
throw new IllegalArgumentException("Parameter type " + parameterTypes.get(notAllowedParameterIndex).name()
+ " is being used multiple times in method" + controllerAdviceMethod.name() + " of class"
+ controllerAdviceMethod.declaringClass().name());
}

// we need to generate a field that injects the HttpServletRequest into the class
FieldCreator httpRequestFieldCreator = cc.getFieldCreator("httpServletRequest", HttpServletRequest.class)
.setModifiers(Modifier.PRIVATE);
httpRequestFieldCreator.addAnnotation(Context.class);

// stash the fieldCreator in a map indexed by the parameter type so we can retrieve it later
parameterTypeToField.put(parameterType, httpRequestFieldCreator.getFieldDescriptor());
} else if (typesUtil.isAssignable(HttpServletResponse.class, parameterTypeDotName)) {
if (parameterTypeToField.containsKey(parameterType)) {
throw new IllegalArgumentException("Parameter type " + parameterTypes.get(notAllowedParameterIndex).name()
+ " is being used multiple times in method" + controllerAdviceMethod.name() + " of class"
+ controllerAdviceMethod.declaringClass().name());
}

// we need to generate a field that injects the HttpServletRequest into the class
FieldCreator httpRequestFieldCreator = cc.getFieldCreator("httpServletResponse", HttpServletResponse.class)
.setModifiers(Modifier.PRIVATE);
httpRequestFieldCreator.addAnnotation(Context.class);

// stash the fieldCreator in a map indexed by the parameter type so we can retrieve it later
parameterTypeToField.put(parameterType, httpRequestFieldCreator.getFieldDescriptor());
} else {
notAllowedParameterIndex = i;
}
}
if (notAllowedParameterIndex >= 0) {
throw new IllegalArgumentException(
"Parameter type " + parameterTypes.get(notAllowedParameterIndex).name() + " is not supported for method"
+ controllerAdviceMethod.name() + " of class" + controllerAdviceMethod.declaringClass().name());
}

createHttpHeadersField(cc);
}

private void createHttpHeadersField(ClassCreator classCreator) {
FieldCreator httpHeadersFieldCreator = classCreator
.getFieldCreator("httpHeaders", HttpHeaders.class)
.setModifiers(Modifier.PRIVATE);
httpHeadersFieldCreator.addAnnotation(Context.class);
httpHeadersField = httpHeadersFieldCreator.getFieldDescriptor();
}

@Override
void generateMethodBody(MethodCreator toResponse) {
if (isVoidType(returnType)) {
Expand Down Expand Up @@ -116,7 +190,7 @@ private ResultHandle getResponseContentType(MethodCreator methodCreator, List<St
return methodCreator.invokeStaticMethod(
MethodDescriptor.ofMethod(ResponseContentTypeResolver.class, "resolve", MediaType.class,
HttpHeaders.class, String[].class),
getBeanFromArc(methodCreator, HttpHeaders.class.getName()),
methodCreator.readInstanceField(httpHeadersField, methodCreator.getThis()),
methodCreator.marshalAsArray(String.class, supportedMediaTypes));
}

Expand All @@ -136,16 +210,9 @@ private ResultHandle invokeExceptionHandlerMethod(MethodCreator toResponse) {
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()
+ "'");
parameterTypeHandles[i] = toResponse.readInstanceField(parameterTypeToField.get(parameterType),
toResponse.getThis());
}
}

Expand All @@ -156,20 +223,18 @@ private ResultHandle invokeExceptionHandlerMethod(MethodCreator toResponse) {
}

private ResultHandle controllerAdviceInstance(MethodCreator toResponse) {
return toResponse.checkCast(getBeanFromArc(toResponse, declaringClassName),
controllerAdviceMethod.declaringClass().name().toString());
}
ResultHandle controllerAdviceClass = toResponse.loadClass(declaringClassName);

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

private int getAnnotationStatusOrDefault(int defaultValue) {
Expand Down
Loading

0 comments on commit 38525a7

Please sign in to comment.