Skip to content

Commit

Permalink
Give extension a way to run code before exception mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
geoand committed Jul 5, 2023
1 parent e153bcc commit 13b66e1
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
import org.jboss.resteasy.reactive.server.core.ServerSerialisers;
import org.jboss.resteasy.reactive.server.handlers.RestInitialHandler;
import org.jboss.resteasy.reactive.server.model.ContextResolvers;
import org.jboss.resteasy.reactive.server.model.DelegatingServerRestHandler;
import org.jboss.resteasy.reactive.server.model.DynamicFeatures;
import org.jboss.resteasy.reactive.server.model.Features;
import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
Expand All @@ -109,6 +110,7 @@
import org.jboss.resteasy.reactive.server.processor.scanning.ResponseStatusMethodScanner;
import org.jboss.resteasy.reactive.server.processor.util.ResteasyReactiveServerDotNames;
import org.jboss.resteasy.reactive.server.spi.RuntimeConfiguration;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyAsyncFileMessageBodyWriter;
import org.jboss.resteasy.reactive.server.vertx.serializers.ServerMutinyBufferMessageBodyWriter;
import org.jboss.resteasy.reactive.server.vertx.serializers.ServerVertxAsyncFileMessageBodyWriter;
Expand Down Expand Up @@ -180,6 +182,7 @@
import io.quarkus.resteasy.reactive.server.spi.HandlerConfigurationProviderBuildItem;
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
import io.quarkus.resteasy.reactive.server.spi.NonBlockingReturnTypeBuildItem;
import io.quarkus.resteasy.reactive.server.spi.PreExceptionMapperHandlerBuildItem;
import io.quarkus.resteasy.reactive.server.spi.ResumeOn404BuildItem;
import io.quarkus.resteasy.reactive.spi.CustomExceptionMapperBuildItem;
import io.quarkus.resteasy.reactive.spi.DynamicFeatureBuildItem;
Expand Down Expand Up @@ -1098,6 +1101,7 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem,
HttpBuildTimeConfig vertxConfig,
SetupEndpointsResultBuildItem setupEndpointsResult,
ServerSerialisersBuildItem serverSerialisersBuildItem,
List<PreExceptionMapperHandlerBuildItem> preExceptionMapperHandlerBuildItems,
List<DynamicFeatureBuildItem> dynamicFeatures,
List<JaxrsFeatureBuildItem> features,
Optional<RequestContextFactoryBuildItem> requestContextFactoryBuildItem,
Expand Down Expand Up @@ -1212,6 +1216,7 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem,
.setFactoryCreator(recorder.factoryCreator(beanContainerBuildItem.getValue()))
.setDynamicFeatures(dynamicFeats)
.setSerialisers(serialisers)
.setPreExceptionMapperHandler(determinePreExceptionMapperHandler(preExceptionMapperHandlerBuildItems))
.setApplicationPath(applicationPath)
.setGlobalHandlerCustomizers(Collections.singletonList(new SecurityContextOverrideHandler.Customizer())) //TODO: should be pluggable
.setResourceClasses(resourceClasses)
Expand Down Expand Up @@ -1282,6 +1287,19 @@ public void setupDeployment(BeanContainerBuildItem beanContainerBuildItem,
}
}

private ServerRestHandler determinePreExceptionMapperHandler(
List<PreExceptionMapperHandlerBuildItem> preExceptionMapperHandlerBuildItems) {
if ((preExceptionMapperHandlerBuildItems == null) || preExceptionMapperHandlerBuildItems.isEmpty()) {
return null;
}
if (preExceptionMapperHandlerBuildItems.size() == 1) {
return preExceptionMapperHandlerBuildItems.get(0).getHandler();
}
Collections.sort(preExceptionMapperHandlerBuildItems);
return new DelegatingServerRestHandler(preExceptionMapperHandlerBuildItems.stream()
.map(PreExceptionMapperHandlerBuildItem::getHandler).collect(toList()));
}

private static boolean notFoundCustomExMapper(String builtInExSignature, String builtInMapperSignature,
ExceptionMapping exceptionMapping) {
for (var entry : exceptionMapping.getMappers().entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package io.quarkus.resteasy.reactive.server.test;

import static io.restassured.RestAssured.get;
import static org.assertj.core.api.Assertions.*;

import java.util.function.Consumer;
import java.util.function.Supplier;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;

import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveContainerRequestContext;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.builder.BuildChainBuilder;
import io.quarkus.builder.BuildContext;
import io.quarkus.builder.BuildStep;
import io.quarkus.resteasy.reactive.server.spi.PreExceptionMapperHandlerBuildItem;
import io.quarkus.test.QuarkusUnitTest;
import io.smallrye.mutiny.Uni;

public class PreExceptionMapperHandlerTest {

@RegisterExtension
static QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
return ShrinkWrap.create(JavaArchive.class)
.addClasses(Resource.class, Mappers.class, DummyPreExceptionMapperHandler.class);
}
}).addBuildChainCustomizer(new Consumer<>() {
@Override
public void accept(BuildChainBuilder buildChainBuilder) {
buildChainBuilder.addBuildStep(new BuildStep() {
@Override
public void execute(BuildContext context) {
context.produce(
new PreExceptionMapperHandlerBuildItem(new DummyPreExceptionMapperHandler()));
}
}).produces(PreExceptionMapperHandlerBuildItem.class).build();
}
});

@Test
public void test() {
get("/test")
.then()
.statusCode(999)
.header("foo", "bar");

get("/test/uni")
.then()
.statusCode(999)
.header("foo", "bar");
}

@Path("test")
public static class Resource {

@GET
public String get() {
throw new RuntimeException("dummy");
}

@Path("uni")
@GET
public Uni<String> uniGet() {
return Uni.createFrom().item(() -> {
throw new RuntimeException("dummy");
});
}
}

public static class Mappers {

@ServerExceptionMapper(RuntimeException.class)
Response handle(ResteasyReactiveContainerRequestContext requestContext) {
return Response.status(999).header("foo", requestContext.getProperty("foo")).build();
}

}

public static class DummyPreExceptionMapperHandler implements ServerRestHandler {

@Override
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
assertThat(requestContext.getThrowable()).isInstanceOf(RuntimeException.class);
requestContext.setProperty("foo", "bar");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.quarkus.resteasy.reactive.server.spi;

import jakarta.ws.rs.Priorities;

import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;

import io.quarkus.builder.item.MultiBuildItem;

/**
* A build item that allows extension to define a {@link ServerRestHandler} that runs write before
* RESTEasy Reactive attempt to do exception mapping according to the JAX-RS spec.
* This is only meant to be used in very advanced use cases.
*/
public final class PreExceptionMapperHandlerBuildItem extends MultiBuildItem
implements Comparable<PreExceptionMapperHandlerBuildItem> {

private final ServerRestHandler handler;
private final int priority;

public PreExceptionMapperHandlerBuildItem(ServerRestHandler handler, int priority) {
this.handler = handler;
this.priority = priority;
}

public PreExceptionMapperHandlerBuildItem(ServerRestHandler handler) {
this.handler = handler;
this.priority = Priorities.USER;
}

@Override
public int compareTo(PreExceptionMapperHandlerBuildItem o) {
return Integer.compare(priority, o.priority);
}

public ServerRestHandler getHandler() {
return handler;
}

public int getPriority() {
return priority;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.jboss.resteasy.reactive.server.model.Features;
import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
import org.jboss.resteasy.reactive.server.model.ParamConverterProviders;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
import org.jboss.resteasy.reactive.spi.BeanFactory;

public class DeploymentInfo {
Expand All @@ -25,6 +26,8 @@ public class DeploymentInfo {
private Features features;
private DynamicFeatures dynamicFeatures;
private ServerSerialisers serialisers;

private ServerRestHandler preExceptionMapperHandler;
private List<ResourceClass> resourceClasses;
private List<ResourceClass> locatableResourceClasses;
private ParamConverterProviders paramConverterProviders;
Expand Down Expand Up @@ -91,6 +94,15 @@ public DeploymentInfo setSerialisers(ServerSerialisers serialisers) {
return this;
}

public ServerRestHandler getPreExceptionMapperHandler() {
return preExceptionMapperHandler;
}

public DeploymentInfo setPreExceptionMapperHandler(ServerRestHandler preExceptionMapperHandler) {
this.preExceptionMapperHandler = preExceptionMapperHandler;
return this;
}

public List<ResourceClass> getResourceClasses() {
return resourceClasses;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@ public BeanFactory.BeanInstance<?> apply(Class<?> aClass) {
if (interceptorDeployment.getGlobalInterceptorHandler() != null) {
abortHandlingChain.add(interceptorDeployment.getGlobalInterceptorHandler());
}
if (info.getPreExceptionMapperHandler() != null) {
abortHandlingChain.add(info.getPreExceptionMapperHandler());
}
abortHandlingChain.add(new ExceptionHandler());
abortHandlingChain.add(ResponseHandler.NO_CUSTOMIZER_INSTANCE);
if (!interceptors.getContainerResponseFilters().getGlobalResourceInterceptors().isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz,
//setup reader and writer interceptors first
ServerRestHandler interceptorHandler = interceptorDeployment.setupInterceptorHandler();
//we want interceptors in the abort handler chain
List<ServerRestHandler> abortHandlingChain = new ArrayList<>(3 + (interceptorHandler != null ? 1 : 0));
List<ServerRestHandler> abortHandlingChain = new ArrayList<>(
3 + (interceptorHandler != null ? 1 : 0) + (info.getPreExceptionMapperHandler() != null ? 1 : 0));

List<ServerRestHandler> handlers = new ArrayList<>(HANDLERS_CAPACITY);
// we add null as the first item to make sure that subsequent items are added in the proper positions
Expand Down Expand Up @@ -487,6 +488,9 @@ public RuntimeResource buildResourceMethod(ResourceClass clazz,
// so we can invoke it
abortHandlingChain.add(instanceHandler);
}
if (info.getPreExceptionMapperHandler() != null) {
abortHandlingChain.add(info.getPreExceptionMapperHandler());
}
abortHandlingChain.add(ExceptionHandler.INSTANCE);
abortHandlingChain.add(ResponseHandler.NO_CUSTOMIZER_INSTANCE);
abortHandlingChain.addAll(responseFilterHandlers);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package org.jboss.resteasy.reactive.server.model;

import java.util.List;

import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;

public class DelegatingServerRestHandler implements ServerRestHandler {

private List<ServerRestHandler> delegates;

public DelegatingServerRestHandler(List<ServerRestHandler> delegates) {
this.delegates = delegates;
}

// for bytecode recording
public DelegatingServerRestHandler() {
}

public List<ServerRestHandler> getDelegates() {
return delegates;
}

public void setDelegates(List<ServerRestHandler> delegates) {
this.delegates = delegates;
}

@Override
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
for (int i = 0; i < delegates.size(); i++) {
delegates.get(0).handle(requestContext);
}
}
}

0 comments on commit 13b66e1

Please sign in to comment.