From 7f09b8c0289c6a915d417a7a7a7e594b319a9b19 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 9 Sep 2024 11:43:08 +0300 Subject: [PATCH 1/3] Allows users to exclude DefaultMismatchedInputException A few users have run into an issue where they are trying to handle all Jackson exceptions but the existence of DefaultMismatchedInputException prevents that for MismatchedInputException (as per JAX-RS / Jakarta REST rules). This change allows users to do use: quarkus.class-loading.removed-resources."io.quarkus\:quarkus-rest-jackson"=io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.class in order to have Quarkus ignore the mapper. --- .../ResteasyReactiveJacksonProcessor.java | 15 ++++-- ...dedBuiltInAndIncludedCustomMapperTest.java | 46 +++++++++++++++++++ .../DefaultMismatchedInputException.java | 1 + 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index 9fa08c45b54d2..8debcec04ede8 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -72,7 +72,6 @@ import io.quarkus.resteasy.reactive.jackson.EnableSecureSerialization; import io.quarkus.resteasy.reactive.jackson.SecureField; import io.quarkus.resteasy.reactive.jackson.runtime.ResteasyReactiveServerJacksonRecorder; -import io.quarkus.resteasy.reactive.jackson.runtime.mappers.DefaultMismatchedInputException; import io.quarkus.resteasy.reactive.jackson.runtime.mappers.NativeInvalidDefinitionExceptionMapper; import io.quarkus.resteasy.reactive.jackson.runtime.security.RolesAllowedConfigExpStorage; import io.quarkus.resteasy.reactive.jackson.runtime.security.SecurityCustomSerialization; @@ -108,6 +107,7 @@ public class ResteasyReactiveJacksonProcessor { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final List HANDLED_MEDIA_TYPES = List.of(MediaType.APPLICATION_JSON, APPLICATION_NDJSON, APPLICATION_STREAM_JSON); + public static final String DEFAULT_MISMATCHED_INPUT_EXCEPTION = "io.quarkus.resteasy.reactive.jackson.runtime.mappers.DefaultMismatchedInputException"; @BuildStep void feature(BuildProducer feature) { @@ -130,9 +130,16 @@ ReinitializeVertxJsonBuildItem vertxJson() { } @BuildStep - ExceptionMapperBuildItem exceptionMappers() { - return new ExceptionMapperBuildItem(DefaultMismatchedInputException.class.getName(), - MismatchedInputException.class.getName(), Priorities.USER + 100, false); + void exceptionMappers(BuildProducer producer) { + try { + Thread.currentThread().getContextClassLoader().loadClass(DEFAULT_MISMATCHED_INPUT_EXCEPTION); + } catch (NoClassDefFoundError | ClassNotFoundException e) { + // the class is not available, likely due to quarkus.class-loading.removed-resources."io.quarkus\:quarkus-rest-jackson"=io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.class + return; + } + + producer.produce(new ExceptionMapperBuildItem(DEFAULT_MISMATCHED_INPUT_EXCEPTION, + MismatchedInputException.class.getName(), Priorities.USER + 100, false)); } @BuildStep diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java new file mode 100644 index 0000000000000..e0ca92a987963 --- /dev/null +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java @@ -0,0 +1,46 @@ +package io.quarkus.resteasy.reactive.jackson.deployment.test; + +import java.util.function.Supplier; + +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; + +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 com.fasterxml.jackson.databind.DatabindException; + +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; + +public class ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(FroMage.class, FroMageEndpoint.class, DatabindExceptionMapper.class); + } + }).overrideConfigKey("quarkus.class-loading.removed-resources.\"io.quarkus\\:quarkus-rest-jackson\"", + "io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.class"); + + @Test + public void test() { + RestAssured.with().contentType("application/json").body("{\"name\": \"brie\"}").put("/fromage") + .then().statusCode(999); + } + + @Provider + public static class DatabindExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(DatabindException exception) { + return Response.status(999).build(); + } + } +} diff --git a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java index 54783b513b961..151ab715d69c4 100644 --- a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java +++ b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java @@ -10,6 +10,7 @@ import io.quarkus.runtime.LaunchMode; +@SuppressWarnings("unused") public class DefaultMismatchedInputException implements ExceptionMapper { From 7a898637e123e4fb8c4c0dc2e715d746c58dfe97 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Mon, 9 Sep 2024 11:51:39 +0300 Subject: [PATCH 2/3] Document how to turn off DefaultMismatchedInputException --- docs/src/main/asciidoc/rest.adoc | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/rest.adoc b/docs/src/main/asciidoc/rest.adoc index 8942727975771..f7fa491a3394e 100644 --- a/docs/src/main/asciidoc/rest.adoc +++ b/docs/src/main/asciidoc/rest.adoc @@ -1415,9 +1415,28 @@ In both cases, importing those modules will allow HTTP message bodies to be read and serialised to JSON, for <>. -==== Advanced Jackson-specific features +==== Jackson-specific features -When using the `quarkus-rest-jackson` extension there are some advanced features that Quarkus REST supports. +===== Exception handling + +By default, Quarkus provides a built-in `ExceptionMapper` for `MismatchedInputException` which returns an HTTP 400 status code +along with a good error message in Dev and Test modes, about what went wrong during serialization of an entity. + +[NOTE] +==== +There are situations where various Jackson related exceptions need to handled in a uniform way.For example, the application may need to handle all `JsonMappingException` the same way. +This becomes a problem when taking JAX-RS / Jakarta REST rules into account, because the exception mapper `ExceptionMapper` for `MismatchedInputException` would be used instead of the user provide +`ExceptionMapper` for `JsonMappingException` (as `MismatchedInputException` is a subtype of `JsonMappingException`). + +One solution for this case is to configure the following: + +[source,properties] +---- +quarkus.class-loading.removed-resources."io.quarkus\:quarkus-rest-jackson"=io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.class +---- + +which essentially makes Quarkus ignore the `ExceptionMapper` for `MismatchedInputException` completely. +==== [[secure-serialization]] ===== Secure serialization From e9cddc0508f5c1c520bf448da66ecd65500de908 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Tue, 10 Sep 2024 14:46:14 +0300 Subject: [PATCH 3/3] Rename DefaultMismatchedInputException to BuiltinMismatchedInputExceptionMapper --- docs/src/main/asciidoc/rest.adoc | 2 +- .../deployment/processor/ResteasyReactiveJacksonProcessor.java | 2 +- ...nInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java | 2 +- ...xception.java => BuiltinMismatchedInputExceptionMapper.java} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/{DefaultMismatchedInputException.java => BuiltinMismatchedInputExceptionMapper.java} (98%) diff --git a/docs/src/main/asciidoc/rest.adoc b/docs/src/main/asciidoc/rest.adoc index f7fa491a3394e..d0e645f412f5e 100644 --- a/docs/src/main/asciidoc/rest.adoc +++ b/docs/src/main/asciidoc/rest.adoc @@ -1432,7 +1432,7 @@ One solution for this case is to configure the following: [source,properties] ---- -quarkus.class-loading.removed-resources."io.quarkus\:quarkus-rest-jackson"=io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.class +quarkus.class-loading.removed-resources."io.quarkus\:quarkus-rest-jackson"=io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.class ---- which essentially makes Quarkus ignore the `ExceptionMapper` for `MismatchedInputException` completely. diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java index 8debcec04ede8..c2a0ec0735be8 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/main/java/io/quarkus/resteasy/reactive/jackson/deployment/processor/ResteasyReactiveJacksonProcessor.java @@ -107,7 +107,7 @@ public class ResteasyReactiveJacksonProcessor { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final List HANDLED_MEDIA_TYPES = List.of(MediaType.APPLICATION_JSON, APPLICATION_NDJSON, APPLICATION_STREAM_JSON); - public static final String DEFAULT_MISMATCHED_INPUT_EXCEPTION = "io.quarkus.resteasy.reactive.jackson.runtime.mappers.DefaultMismatchedInputException"; + public static final String DEFAULT_MISMATCHED_INPUT_EXCEPTION = "io.quarkus.resteasy.reactive.jackson.runtime.mappers.BuiltinMismatchedInputExceptionMapper"; @BuildStep void feature(BuildProducer feature) { diff --git a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java index e0ca92a987963..e334002585c07 100644 --- a/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java +++ b/extensions/resteasy-reactive/rest-jackson/deployment/src/test/java/io/quarkus/resteasy/reactive/jackson/deployment/test/ExceptionInReaderWithExcludedBuiltInAndIncludedCustomMapperTest.java @@ -27,7 +27,7 @@ public JavaArchive get() { .addClasses(FroMage.class, FroMageEndpoint.class, DatabindExceptionMapper.class); } }).overrideConfigKey("quarkus.class-loading.removed-resources.\"io.quarkus\\:quarkus-rest-jackson\"", - "io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.class"); + "io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.class"); @Test public void test() { diff --git a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.java similarity index 98% rename from extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java rename to extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.java index 151ab715d69c4..e9fe48c834fca 100644 --- a/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/DefaultMismatchedInputException.java +++ b/extensions/resteasy-reactive/rest-jackson/runtime/src/main/java/io/quarkus/resteasy/reactive/jackson/runtime/mappers/BuiltinMismatchedInputExceptionMapper.java @@ -11,7 +11,7 @@ import io.quarkus.runtime.LaunchMode; @SuppressWarnings("unused") -public class DefaultMismatchedInputException +public class BuiltinMismatchedInputExceptionMapper implements ExceptionMapper { @Override