diff --git a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java
index 71c2239c4..266bf9494 100755
--- a/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java
+++ b/extensions/jackson/deployment/src/main/java/io/quarkus/jackson/deployment/JacksonProcessor.java
@@ -232,6 +232,11 @@ public class JacksonProcessor {
customize.returnValue(null);
}
+
+ // ensure that the things we auto-register have the lower priority - this ensures that user registered modules take priority
+ try (MethodCreator priority = classCreator.getMethodCreator("priority", int.class)) {
+ priority.returnValue(priority.load(ObjectMapperCustomizer.MINIMUM_PRIORITY));
+ }
}
}
}
diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java
index 789d4625d..0aac835a0 100644
--- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java
+++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperCustomizer.java
@@ -10,7 +10,22 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*
* See also {@link ObjectMapperProducer#objectMapper}.
*/
-public interface ObjectMapperCustomizer {
+public interface ObjectMapperCustomizer extends Comparable {
+
+ int MINIMUM_PRIORITY = Integer.MIN_VALUE;
+ int DEFAULT_PRIORITY = 0;
void customize(ObjectMapper objectMapper);
+
+ /**
+ * Defines the priority that the customizers are applied.
+ * A lower integer value means that the customizer will be applied after a customizer with a higher priority
+ */
+ default int priority() {
+ return DEFAULT_PRIORITY;
+ }
+
+ default int compareTo(ObjectMapperCustomizer o) {
+ return Integer.compare(o.priority(), priority());
+ }
}
diff --git a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java
index ddc397f7e..c017ba5e7 100644
--- a/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java
+++ b/extensions/jackson/runtime/src/main/java/io/quarkus/jackson/ObjectMapperProducer.java
@@ -1,5 +1,9 @@
package io.quarkus.jackson;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Instance;
import javax.enterprise.inject.Produces;
@@ -17,9 +21,20 @@ public class ObjectMapperProducer {
@Produces
public ObjectMapper objectMapper(Instance customizers) {
ObjectMapper objectMapper = new ObjectMapper();
- for (ObjectMapperCustomizer customizer : customizers) {
+ List sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers);
+ for (ObjectMapperCustomizer customizer : sortedCustomizers) {
customizer.customize(objectMapper);
}
return objectMapper;
}
+
+ private List sortCustomizersInDescendingPriorityOrder(
+ Instance customizers) {
+ List sortedCustomizers = new ArrayList<>();
+ for (ObjectMapperCustomizer customizer : customizers) {
+ sortedCustomizers.add(customizer);
+ }
+ Collections.sort(sortedCustomizers);
+ return sortedCustomizers;
+ }
}
diff --git a/extensions/resteasy-jackson/deployment/pom.xml b/extensions/resteasy-jackson/deployment/pom.xml
index 689259d98..eeee12900 100644
--- a/extensions/resteasy-jackson/deployment/pom.xml
+++ b/extensions/resteasy-jackson/deployment/pom.xml
@@ -26,6 +26,16 @@
io.quarkus
quarkus-resteasy-jackson
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
diff --git a/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/DateDto.java b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/DateDto.java
new file mode 100644
index 000000000..bc3022089
--- /dev/null
+++ b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/DateDto.java
@@ -0,0 +1,22 @@
+package io.quarkus.resteasy.jackson;
+
+import java.time.ZonedDateTime;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class DateDto {
+ @JsonProperty("current_date")
+ private ZonedDateTime currentDate;
+
+ public void setCurrentDate(ZonedDateTime currentDate) {
+ this.currentDate = currentDate;
+ }
+
+ public ZonedDateTime getCurrentDate() {
+ return currentDate;
+ }
+
+ public DateDto(ZonedDateTime currentDate) {
+ this.currentDate = currentDate;
+ }
+}
diff --git a/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/HelloResource.java b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/HelloResource.java
new file mode 100644
index 000000000..924a6ca63
--- /dev/null
+++ b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/HelloResource.java
@@ -0,0 +1,18 @@
+package io.quarkus.resteasy.jackson;
+
+import java.time.ZonedDateTime;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Produces(MediaType.APPLICATION_JSON)
+@Path("/hello")
+public class HelloResource {
+
+ @GET
+ public DateDto hello() {
+ return new DateDto(ZonedDateTime.now());
+ }
+}
diff --git a/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/MultipleTimeModuleTest.java b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/MultipleTimeModuleTest.java
new file mode 100644
index 000000000..5d78b59f6
--- /dev/null
+++ b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/MultipleTimeModuleTest.java
@@ -0,0 +1,47 @@
+package io.quarkus.resteasy.jackson;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+
+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.test.QuarkusDevModeTest;
+import io.restassured.RestAssured;
+
+// this test really belongs in the jackson module, but it's been added here to avoid test classpath issues
+public class MultipleTimeModuleTest {
+
+ @RegisterExtension
+ static QuarkusDevModeTest TEST = new QuarkusDevModeTest()
+ .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
+ .addClasses(TimeCustomizer.class, DateDto.class, HelloResource.class));
+
+ @Test
+ public void testDateIsAlwaysInTheExpectedFormat() {
+ verifyExpectedResult();
+
+ modifyResource();
+ verifyExpectedResult();
+
+ modifyResource();
+ verifyExpectedResult();
+
+ modifyResource();
+ verifyExpectedResult();
+ }
+
+ private void verifyExpectedResult() {
+ RestAssured.get("/hello").then()
+ .statusCode(200)
+ .body(containsString("Z"), not(containsString("+")));
+ }
+
+ private void modifyResource() {
+ TEST.modifySourceFile("TimeCustomizer.java", s -> s.replace("hello",
+ "hello2"));
+ }
+
+}
diff --git a/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/TimeCustomizer.java b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/TimeCustomizer.java
new file mode 100644
index 000000000..8d2678140
--- /dev/null
+++ b/extensions/resteasy-jackson/deployment/src/test/java/io/quarkus/resteasy/jackson/TimeCustomizer.java
@@ -0,0 +1,27 @@
+package io.quarkus.resteasy.jackson;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatterBuilder;
+
+import javax.inject.Singleton;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer;
+
+import io.quarkus.jackson.ObjectMapperCustomizer;
+
+@Singleton
+public class TimeCustomizer implements ObjectMapperCustomizer {
+
+ @Override
+ public void customize(ObjectMapper objectMapper) {
+ JavaTimeModule customDateModule = new JavaTimeModule();
+ customDateModule.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer(
+ new DateTimeFormatterBuilder().appendInstant(0).toFormatter().withZone(ZoneId.of("Z"))));
+ objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
+ .registerModule(customDateModule);
+ }
+}