mirror of
https://github.com/jlengrand/quarkus.git
synced 2026-03-10 08:41:22 +00:00
Ensure that user provided ObjectMapperCustomizer objects always have higher priority
Fixes: #7116
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,22 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
* <p>
|
||||
* See also {@link ObjectMapperProducer#objectMapper}.
|
||||
*/
|
||||
public interface ObjectMapperCustomizer {
|
||||
public interface ObjectMapperCustomizer extends Comparable<ObjectMapperCustomizer> {
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ObjectMapperCustomizer> customizers) {
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
for (ObjectMapperCustomizer customizer : customizers) {
|
||||
List<ObjectMapperCustomizer> sortedCustomizers = sortCustomizersInDescendingPriorityOrder(customizers);
|
||||
for (ObjectMapperCustomizer customizer : sortedCustomizers) {
|
||||
customizer.customize(objectMapper);
|
||||
}
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
private List<ObjectMapperCustomizer> sortCustomizersInDescendingPriorityOrder(
|
||||
Instance<ObjectMapperCustomizer> customizers) {
|
||||
List<ObjectMapperCustomizer> sortedCustomizers = new ArrayList<>();
|
||||
for (ObjectMapperCustomizer customizer : customizers) {
|
||||
sortedCustomizers.add(customizer);
|
||||
}
|
||||
Collections.sort(sortedCustomizers);
|
||||
return sortedCustomizers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,16 @@
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-resteasy-jackson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.quarkus</groupId>
|
||||
<artifactId>quarkus-junit5-internal</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.rest-assured</groupId>
|
||||
<artifactId>rest-assured</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user