Ensure that user provided ObjectMapperCustomizer objects always have higher priority

Fixes: #7116
This commit is contained in:
Georgios Andrianakis
2020-02-11 16:29:42 +02:00
parent 10b8da6a17
commit fe27eb7217
8 changed files with 161 additions and 2 deletions

View File

@@ -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));
}
}
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -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"));
}
}

View File

@@ -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);
}
}