From 8cbe30ab5eb72cdc005c7ec57cdf912ca929de7b Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Mon, 4 Jul 2016 14:23:36 +0100 Subject: [PATCH] Ensure that Jersey and Jackson honour JAXB annotations By default Jersey configures Jackson to use both Jackson annotations and JAXB annotations when introspective types for (de)serialization. However, the changes made in 5776d6a8 mean that Jersey no longer uses its default ObjectMapper configuration and uses the auto-configured ObjectMapper instead. This had the unwanted side-effect of leaving Jersey with an ObjectMapper that only uses Jackson annotations and ignores JAXB annotations. This commit updates JerseyAutoConfiguration so that it will add the JaxbAnnotationIntrospector to the auto-configured ObjectMapper for both serialization and deserialization. It uses AnnotationIntrospectorPair to ensure retain any existing introspectors. Closes gh-6310 --- .../jersey/JerseyAutoConfiguration.java | 51 ++++++++++++++++--- ...onfigurationObjectMapperProviderTests.java | 15 ++++-- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java index 1ad876886e..348e09e822 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfiguration.java @@ -28,9 +28,11 @@ import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import javax.ws.rs.ApplicationPath; import javax.ws.rs.ext.ContextResolver; -import javax.ws.rs.ext.Provider; +import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.MapperConfig; +import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.glassfish.jersey.CommonProperties; @@ -40,7 +42,6 @@ import org.glassfish.jersey.servlet.ServletContainer; import org.glassfish.jersey.servlet.ServletProperties; import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.AutoConfigureOrder; @@ -234,23 +235,57 @@ public class JerseyAutoConfiguration implements ServletContextAware { @Configuration static class JacksonResourceConfigCustomizer { + private static final String JAXB_ANNOTATION_INTROSPECTOR_CLASS_NAME = "com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector"; + @Bean - public ResourceConfigCustomizer resourceConfigCustomizer() { + public ResourceConfigCustomizer resourceConfigCustomizer( + final ObjectMapper objectMapper) { + addJaxbAnnotationIntrospectorIfPresent(objectMapper); return new ResourceConfigCustomizer() { @Override public void customize(ResourceConfig config) { config.register(JacksonFeature.class); - config.register(ObjectMapperContextResolver.class); + config.register(new ObjectMapperContextResolver(objectMapper), + ContextResolver.class); } }; } - @Provider - static class ObjectMapperContextResolver + private void addJaxbAnnotationIntrospectorIfPresent(ObjectMapper objectMapper) { + if (ClassUtils.isPresent(JAXB_ANNOTATION_INTROSPECTOR_CLASS_NAME, + getClass().getClassLoader())) { + new ObjectMapperCustomizer().addJaxbAnnotationIntrospector(objectMapper); + } + } + + private static final class ObjectMapperCustomizer { + + private void addJaxbAnnotationIntrospector(ObjectMapper objectMapper) { + JaxbAnnotationIntrospector jaxbAnnotationIntrospector = new JaxbAnnotationIntrospector( + objectMapper.getTypeFactory()); + objectMapper.setAnnotationIntrospectors( + createPair(objectMapper.getSerializationConfig(), + jaxbAnnotationIntrospector), + createPair(objectMapper.getDeserializationConfig(), + jaxbAnnotationIntrospector)); + } + + private AnnotationIntrospector createPair(MapperConfig config, + JaxbAnnotationIntrospector jaxbAnnotationIntrospector) { + return AnnotationIntrospector.pair(config.getAnnotationIntrospector(), + jaxbAnnotationIntrospector); + } + + } + + private static final class ObjectMapperContextResolver implements ContextResolver { - @Autowired - private ObjectMapper objectMapper; + private final ObjectMapper objectMapper; + + private ObjectMapperContextResolver(ObjectMapper objectMapper) { + this.objectMapper = objectMapper; + } @Override public ObjectMapper getContext(Class type) { diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java index 9531a98976..2fa8456620 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jersey/JerseyAutoConfigurationObjectMapperProviderTests.java @@ -25,6 +25,7 @@ import java.lang.annotation.Target; import javax.ws.rs.ApplicationPath; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.xml.bind.annotation.XmlTransient; import org.glassfish.jersey.server.ResourceConfig; import org.junit.Test; @@ -52,9 +53,10 @@ import static org.assertj.core.api.Assertions.assertThat; * Tests for {@link JerseyAutoConfiguration} with a ObjectMapper. * * @author EddĂș MelĂ©ndez + * @author Andy Wilkinson */ @RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT, properties = "spring.jackson.serialization-inclusion:non-null") @DirtiesContext public class JerseyAutoConfigurationObjectMapperProviderTests { @@ -62,12 +64,12 @@ public class JerseyAutoConfigurationObjectMapperProviderTests { private TestRestTemplate restTemplate; @Test - public void contextLoads() { + public void responseIsSerializedUsingAutoConfiguredObjectMapper() { ResponseEntity response = this.restTemplate.getForEntity("/rest/message", String.class); assertThat(HttpStatus.OK).isEqualTo(response.getStatusCode()); - assertThat("{\"subject\":\"Jersey\",\"body\":null}") - .isEqualTo(response.getBody()); + assertThat(response.getBody()) + .isEqualTo(String.format("{\"subject\":\"Jersey\"}")); } @MinimalWebConfiguration @@ -121,6 +123,11 @@ public class JerseyAutoConfigurationObjectMapperProviderTests { this.body = body; } + @XmlTransient + public String getFoo() { + return "foo"; + } + } @Target(ElementType.TYPE)