Skip to content

Commit

Permalink
Apply HATEOAS module to primary ObjectMapper
Browse files Browse the repository at this point in the history
Update HypermediaAutoConfiguration to apply the Jackson2HalModule to
the primary ObjectMapper. This restores the behavior of Spring Boot
1.1 where HATEOAS types could be serialized for both `application/json`
and `application/json+hal` content types.

A `spring.hateoas.apply-to-primary-object-mapper` property has also been
provided to opt-out if necessary.

Fixes gh-2147
  • Loading branch information
philwebb committed Dec 23, 2014
1 parent ea84479 commit fd97c75
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2012-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.boot.autoconfigure.hateoas;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* {@link ConfigurationProperties properties} for Spring HATEOAS.
*
* @author Phillip webb
* @since 1.2.1
*/
@ConfigurationProperties(prefix = "spring.hateoas")
public class HateoasProperties {

/**
* If HATEOAS support should be applied to the primary ObjectMapper.
*/
private boolean applyToPrimaryObjectMapper = true;

public boolean isApplyToPrimaryObjectMapper() {
return this.applyToPrimaryObjectMapper;
}

public void setApplyToPrimaryObjectMapper(boolean applyToPrimaryObjectMapper) {
this.applyToPrimaryObjectMapper = applyToPrimaryObjectMapper;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@

package org.springframework.boot.autoconfigure.hateoas;

import javax.annotation.PostConstruct;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
Expand All @@ -27,14 +33,18 @@
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.config.EnableEntityLinks;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.plugin.core.Plugin;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -55,6 +65,7 @@
@ConditionalOnWebApplication
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
HttpMessageConvertersAutoConfiguration.class })
@EnableConfigurationProperties(HateoasProperties.class)
public class HypermediaAutoConfiguration {

@Configuration
Expand All @@ -65,40 +76,90 @@ protected static class HypermediaConfiguration {
@ConditionalOnClass({ Jackson2ObjectMapperBuilder.class, ObjectMapper.class })
protected static class HalObjectMapperConfiguration {

@Autowired
private HateoasProperties hateoasProperties;

@Autowired(required = false)
private Jackson2ObjectMapperBuilder objectMapperBuilder;
private CurieProvider curieProvider;

@Autowired
@Qualifier("_relProvider")
private RelProvider relProvider;

@Autowired(required = false)
private ObjectMapper primaryObjectMapper;

@PostConstruct
public void configurePrimaryObjectMapper() {
if (this.primaryObjectMapper != null
&& this.hateoasProperties.isApplyToPrimaryObjectMapper()) {
registerHalModule(this.primaryObjectMapper);
}
}

private void registerHalModule(ObjectMapper objectMapper) {
objectMapper.registerModule(new Jackson2HalModule());
Jackson2HalModule.HalHandlerInstantiator instantiator = new Jackson2HalModule.HalHandlerInstantiator(
HalObjectMapperConfiguration.this.relProvider,
HalObjectMapperConfiguration.this.curieProvider);
objectMapper.setHandlerInstantiator(instantiator);
}

@Bean
public BeanPostProcessor halObjectMapperConfigurer() {
return new BeanPostProcessor() {

@Override
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
if (HalObjectMapperConfiguration.this.objectMapperBuilder != null
&& bean instanceof ObjectMapper
&& "_halObjectMapper".equals(beanName)) {
HalObjectMapperConfiguration.this.objectMapperBuilder
.configure((ObjectMapper) bean);
}
return bean;
}

@Override
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
return bean;
}

};
public static HalObjectMapperConfigurer halObjectMapperConfigurer() {
return new HalObjectMapperConfigurer();
}

}

}

@Configuration
@ConditionalOnMissingBean(EntityLinks.class)
@EnableEntityLinks
protected static class EntityLinksConfiguration {

}

/**
* {@link BeanPostProcessor} to apply any {@link Jackson2ObjectMapperBuilder}
* configuration to the HAL {@link ObjectMapper}.
*/
private static class HalObjectMapperConfigurer implements BeanPostProcessor,
BeanFactoryAware {

private BeanFactory beanFactory;

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ObjectMapper && "_halObjectMapper".equals(beanName)) {
postProcessHalObjectMapper((ObjectMapper) bean);
}
return bean;
}

private void postProcessHalObjectMapper(ObjectMapper objectMapper) {
try {
Jackson2ObjectMapperBuilder builder = this.beanFactory
.getBean(Jackson2ObjectMapperBuilder.class);
builder.configure(objectMapper);
}
catch (NoSuchBeanDefinitionException ex) {
// No Jackson configuration required
}
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,17 @@ public void entityLinksCreated() throws Exception {

@Test
public void doesBackOffIfEnableHypermediaSupportIsDeclaredManually() {

this.context = new AnnotationConfigWebApplicationContext();
this.context.register(SampleConfig.class, HypermediaAutoConfiguration.class);
this.context.refresh();

this.context.getBean(LinkDiscoverers.class);
}

@Test
public void jacksonConfigurationIsAppliedToTheHalObjectMapper() {
this.context = new AnnotationConfigWebApplicationContext();
this.context.register(HypermediaAutoConfiguration.class,
JacksonAutoConfiguration.class);
this.context.register(JacksonAutoConfiguration.class,
HypermediaAutoConfiguration.class);
EnvironmentTestUtils.addEnvironment(this.context,
"spring.jackson.serialization.INDENT_OUTPUT:true");
this.context.refresh();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ content into your application; rather pick only the properties that you need.
spring.resources.cache-period= # cache timeouts in headers sent to browser
spring.resources.add-mappings=true # if default mappings should be added
# SPRING HATEOS ({sc-spring-boot-autoconfigure}/hateoas/HateoasProperties.{sc-ext}[HateoasProperties])
spring.hateoas.apply-to-primary-object-mapper=true # if the primary mapper should also be configured
# HTTP encoding ({sc-spring-boot-autoconfigure}/web/HttpEncodingProperties.{sc-ext}[HttpEncodingProperties])
spring.http.encoding.charset=UTF-8 # the encoding of HTTP requests/responses
spring.http.encoding.enabled=true # enable http encoding support
Expand Down

0 comments on commit fd97c75

Please sign in to comment.