Skip to content

Commit fd97c75

Browse files
committed
Apply HATEOAS module to primary ObjectMapper
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
1 parent ea84479 commit fd97c75

File tree

4 files changed

+132
-27
lines changed

4 files changed

+132
-27
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2012-2014 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.hateoas;
18+
19+
import org.springframework.boot.context.properties.ConfigurationProperties;
20+
21+
/**
22+
* {@link ConfigurationProperties properties} for Spring HATEOAS.
23+
*
24+
* @author Phillip webb
25+
* @since 1.2.1
26+
*/
27+
@ConfigurationProperties(prefix = "spring.hateoas")
28+
public class HateoasProperties {
29+
30+
/**
31+
* If HATEOAS support should be applied to the primary ObjectMapper.
32+
*/
33+
private boolean applyToPrimaryObjectMapper = true;
34+
35+
public boolean isApplyToPrimaryObjectMapper() {
36+
return this.applyToPrimaryObjectMapper;
37+
}
38+
39+
public void setApplyToPrimaryObjectMapper(boolean applyToPrimaryObjectMapper) {
40+
this.applyToPrimaryObjectMapper = applyToPrimaryObjectMapper;
41+
}
42+
43+
}

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfiguration.java

+84-23
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,14 @@
1616

1717
package org.springframework.boot.autoconfigure.hateoas;
1818

19+
import javax.annotation.PostConstruct;
20+
1921
import org.springframework.beans.BeansException;
22+
import org.springframework.beans.factory.BeanFactory;
23+
import org.springframework.beans.factory.BeanFactoryAware;
24+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2025
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.beans.factory.annotation.Qualifier;
2127
import org.springframework.beans.factory.config.BeanPostProcessor;
2228
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2329
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
@@ -27,14 +33,18 @@
2733
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
2834
import org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration;
2935
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
36+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3037
import org.springframework.context.annotation.Bean;
3138
import org.springframework.context.annotation.Configuration;
3239
import org.springframework.hateoas.EntityLinks;
3340
import org.springframework.hateoas.LinkDiscoverers;
41+
import org.springframework.hateoas.RelProvider;
3442
import org.springframework.hateoas.Resource;
3543
import org.springframework.hateoas.config.EnableEntityLinks;
3644
import org.springframework.hateoas.config.EnableHypermediaSupport;
3745
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
46+
import org.springframework.hateoas.hal.CurieProvider;
47+
import org.springframework.hateoas.hal.Jackson2HalModule;
3848
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
3949
import org.springframework.plugin.core.Plugin;
4050
import org.springframework.web.bind.annotation.RequestMapping;
@@ -55,6 +65,7 @@
5565
@ConditionalOnWebApplication
5666
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, JacksonAutoConfiguration.class,
5767
HttpMessageConvertersAutoConfiguration.class })
68+
@EnableConfigurationProperties(HateoasProperties.class)
5869
public class HypermediaAutoConfiguration {
5970

6071
@Configuration
@@ -65,40 +76,90 @@ protected static class HypermediaConfiguration {
6576
@ConditionalOnClass({ Jackson2ObjectMapperBuilder.class, ObjectMapper.class })
6677
protected static class HalObjectMapperConfiguration {
6778

79+
@Autowired
80+
private HateoasProperties hateoasProperties;
81+
6882
@Autowired(required = false)
69-
private Jackson2ObjectMapperBuilder objectMapperBuilder;
83+
private CurieProvider curieProvider;
84+
85+
@Autowired
86+
@Qualifier("_relProvider")
87+
private RelProvider relProvider;
88+
89+
@Autowired(required = false)
90+
private ObjectMapper primaryObjectMapper;
91+
92+
@PostConstruct
93+
public void configurePrimaryObjectMapper() {
94+
if (this.primaryObjectMapper != null
95+
&& this.hateoasProperties.isApplyToPrimaryObjectMapper()) {
96+
registerHalModule(this.primaryObjectMapper);
97+
}
98+
}
99+
100+
private void registerHalModule(ObjectMapper objectMapper) {
101+
objectMapper.registerModule(new Jackson2HalModule());
102+
Jackson2HalModule.HalHandlerInstantiator instantiator = new Jackson2HalModule.HalHandlerInstantiator(
103+
HalObjectMapperConfiguration.this.relProvider,
104+
HalObjectMapperConfiguration.this.curieProvider);
105+
objectMapper.setHandlerInstantiator(instantiator);
106+
}
70107

71108
@Bean
72-
public BeanPostProcessor halObjectMapperConfigurer() {
73-
return new BeanPostProcessor() {
74-
75-
@Override
76-
public Object postProcessAfterInitialization(Object bean,
77-
String beanName) throws BeansException {
78-
if (HalObjectMapperConfiguration.this.objectMapperBuilder != null
79-
&& bean instanceof ObjectMapper
80-
&& "_halObjectMapper".equals(beanName)) {
81-
HalObjectMapperConfiguration.this.objectMapperBuilder
82-
.configure((ObjectMapper) bean);
83-
}
84-
return bean;
85-
}
86-
87-
@Override
88-
public Object postProcessBeforeInitialization(Object bean,
89-
String beanName) throws BeansException {
90-
return bean;
91-
}
92-
93-
};
109+
public static HalObjectMapperConfigurer halObjectMapperConfigurer() {
110+
return new HalObjectMapperConfigurer();
94111
}
112+
95113
}
114+
96115
}
97116

98117
@Configuration
99118
@ConditionalOnMissingBean(EntityLinks.class)
100119
@EnableEntityLinks
101120
protected static class EntityLinksConfiguration {
121+
102122
}
103123

124+
/**
125+
* {@link BeanPostProcessor} to apply any {@link Jackson2ObjectMapperBuilder}
126+
* configuration to the HAL {@link ObjectMapper}.
127+
*/
128+
private static class HalObjectMapperConfigurer implements BeanPostProcessor,
129+
BeanFactoryAware {
130+
131+
private BeanFactory beanFactory;
132+
133+
@Override
134+
public Object postProcessBeforeInitialization(Object bean, String beanName)
135+
throws BeansException {
136+
if (bean instanceof ObjectMapper && "_halObjectMapper".equals(beanName)) {
137+
postProcessHalObjectMapper((ObjectMapper) bean);
138+
}
139+
return bean;
140+
}
141+
142+
private void postProcessHalObjectMapper(ObjectMapper objectMapper) {
143+
try {
144+
Jackson2ObjectMapperBuilder builder = this.beanFactory
145+
.getBean(Jackson2ObjectMapperBuilder.class);
146+
builder.configure(objectMapper);
147+
}
148+
catch (NoSuchBeanDefinitionException ex) {
149+
// No Jackson configuration required
150+
}
151+
}
152+
153+
@Override
154+
public Object postProcessAfterInitialization(Object bean, String beanName)
155+
throws BeansException {
156+
return bean;
157+
}
158+
159+
@Override
160+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
161+
this.beanFactory = beanFactory;
162+
}
163+
164+
}
104165
}

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/hateoas/HypermediaAutoConfigurationTests.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -75,19 +75,17 @@ public void entityLinksCreated() throws Exception {
7575

7676
@Test
7777
public void doesBackOffIfEnableHypermediaSupportIsDeclaredManually() {
78-
7978
this.context = new AnnotationConfigWebApplicationContext();
8079
this.context.register(SampleConfig.class, HypermediaAutoConfiguration.class);
8180
this.context.refresh();
82-
8381
this.context.getBean(LinkDiscoverers.class);
8482
}
8583

8684
@Test
8785
public void jacksonConfigurationIsAppliedToTheHalObjectMapper() {
8886
this.context = new AnnotationConfigWebApplicationContext();
89-
this.context.register(HypermediaAutoConfiguration.class,
90-
JacksonAutoConfiguration.class);
87+
this.context.register(JacksonAutoConfiguration.class,
88+
HypermediaAutoConfiguration.class);
9189
EnvironmentTestUtils.addEnvironment(this.context,
9290
"spring.jackson.serialization.INDENT_OUTPUT:true");
9391
this.context.refresh();

spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ content into your application; rather pick only the properties that you need.
9696
spring.resources.cache-period= # cache timeouts in headers sent to browser
9797
spring.resources.add-mappings=true # if default mappings should be added
9898
99+
# SPRING HATEOS ({sc-spring-boot-autoconfigure}/hateoas/HateoasProperties.{sc-ext}[HateoasProperties])
100+
spring.hateoas.apply-to-primary-object-mapper=true # if the primary mapper should also be configured
101+
99102
# HTTP encoding ({sc-spring-boot-autoconfigure}/web/HttpEncodingProperties.{sc-ext}[HttpEncodingProperties])
100103
spring.http.encoding.charset=UTF-8 # the encoding of HTTP requests/responses
101104
spring.http.encoding.enabled=true # enable http encoding support

0 commit comments

Comments
 (0)