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
pull/6323/head
Andy Wilkinson 8 years ago
parent ce3f4bd068
commit 8cbe30ab5e

@ -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<ObjectMapper> {
@Autowired
private ObjectMapper objectMapper;
private final ObjectMapper objectMapper;
private ObjectMapperContextResolver(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public ObjectMapper getContext(Class<?> type) {

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

Loading…
Cancel
Save