diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HateoasProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HateoasProperties.java new file mode 100644 index 000000000000..3b314ad3f680 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HateoasProperties.java @@ -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; + } + +} diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java index faf050c6f942..b3c1fff2e472 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java @@ -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; @@ -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; @@ -55,6 +65,7 @@ @ConditionalOnWebApplication @AutoConfigureAfter({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class }) +@EnableConfigurationProperties(HateoasProperties.class) public class HypermediaAutoConfiguration { @Configuration @@ -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; + } + + } } diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java index 961449fcc2f0..8c5655c9efde 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java @@ -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(); diff --git a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index e56fcfb84d1f..1a1c16597cc2 100644 --- a/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -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