Skip to content

Commit 80d13ed

Browse files
committed
#723 - Simplified configuration setup.
We now avoid to register ObjectMapper instances as Spring beans and rather use one already existing in the ApplicationContext and copying the setup before registering the individual HttpMessageConverters for the individual media types. Replaced a lot of the programmatic component setup via BeanDefinitions with their corresponding JavaConfig alternatives. Removed obsolete media type specific HttpMessageConverters and configuration classes registering them. Backport of fix for #719.
1 parent a1683d3 commit 80d13ed

10 files changed

+465
-305
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2018 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+
package org.springframework.hateoas.config;
17+
18+
import lombok.RequiredArgsConstructor;
19+
20+
import org.springframework.beans.BeansException;
21+
import org.springframework.beans.factory.ObjectFactory;
22+
import org.springframework.beans.factory.config.BeanPostProcessor;
23+
import org.springframework.context.ApplicationContext;
24+
import org.springframework.hateoas.hal.Jackson2HalModule;
25+
import org.springframework.web.client.RestTemplate;
26+
27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
29+
/**
30+
* {@link BeanPostProcessor} to register {@link Jackson2HalModule} with {@link ObjectMapper} instances registered in the
31+
* {@link ApplicationContext}.
32+
*
33+
* @author Oliver Gierke
34+
*/
35+
@RequiredArgsConstructor
36+
class ConverterRegisteringBeanPostProcessor implements BeanPostProcessor {
37+
38+
private final ObjectFactory<ConverterRegisteringWebMvcConfigurer> configurer;
39+
40+
/*
41+
* (non-Javadoc)
42+
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
43+
*/
44+
@Override
45+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
46+
47+
if (bean instanceof RestTemplate) {
48+
49+
ConverterRegisteringWebMvcConfigurer object = configurer.getObject();
50+
object.extendMessageConverters(((RestTemplate) bean).getMessageConverters());
51+
}
52+
53+
return bean;
54+
}
55+
56+
/*
57+
* (non-Javadoc)
58+
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
59+
*/
60+
@Override
61+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
62+
return bean;
63+
}
64+
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* Copyright 2018 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+
package org.springframework.hateoas.config;
17+
18+
import static org.springframework.hateoas.MediaTypes.*;
19+
20+
import lombok.RequiredArgsConstructor;
21+
22+
import java.util.Arrays;
23+
import java.util.Collection;
24+
import java.util.List;
25+
26+
import org.springframework.beans.BeansException;
27+
import org.springframework.beans.factory.BeanFactory;
28+
import org.springframework.beans.factory.BeanFactoryAware;
29+
import org.springframework.beans.factory.ObjectProvider;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.context.support.MessageSourceAccessor;
32+
import org.springframework.hateoas.RelProvider;
33+
import org.springframework.hateoas.ResourceSupport;
34+
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
35+
import org.springframework.hateoas.core.DelegatingRelProvider;
36+
import org.springframework.hateoas.hal.CurieProvider;
37+
import org.springframework.hateoas.hal.HalConfiguration;
38+
import org.springframework.hateoas.hal.Jackson2HalModule;
39+
import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator;
40+
import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
41+
import org.springframework.http.converter.HttpMessageConverter;
42+
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
43+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
44+
45+
import com.fasterxml.jackson.databind.DeserializationFeature;
46+
import com.fasterxml.jackson.databind.ObjectMapper;
47+
48+
/**
49+
* @author Oliver Gierke
50+
*/
51+
@Configuration
52+
@RequiredArgsConstructor
53+
public class ConverterRegisteringWebMvcConfigurer extends WebMvcConfigurerAdapter implements BeanFactoryAware {
54+
55+
private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource";
56+
57+
private final ObjectProvider<ObjectMapper> mapper;
58+
private final ObjectProvider<DelegatingRelProvider> relProvider;
59+
private final ObjectProvider<CurieProvider> curieProvider;
60+
private final ObjectProvider<HalConfiguration> halConfiguration;
61+
62+
private BeanFactory beanFactory;
63+
private Collection<HypermediaType> hypermediaTypes;
64+
65+
/*
66+
* (non-Javadoc)
67+
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
68+
*/
69+
@Override
70+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
71+
this.beanFactory = beanFactory;
72+
}
73+
74+
/**
75+
* @param hyperMediaTypes the hyperMediaTypes to set
76+
*/
77+
public void setHypermediaTypes(Collection<HypermediaType> hyperMediaTypes) {
78+
this.hypermediaTypes = hyperMediaTypes;
79+
}
80+
81+
/*
82+
* (non-Javadoc)
83+
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#extendMessageConverters(java.util.List)
84+
*/
85+
@Override
86+
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
87+
88+
for (HttpMessageConverter<?> converter : converters) {
89+
if (converter instanceof MappingJackson2HttpMessageConverter) {
90+
MappingJackson2HttpMessageConverter halConverterCandidate = (MappingJackson2HttpMessageConverter) converter;
91+
ObjectMapper objectMapper = halConverterCandidate.getObjectMapper();
92+
if (Jackson2HalModule.isAlreadyRegisteredIn(objectMapper)) {
93+
return;
94+
}
95+
}
96+
}
97+
98+
ObjectMapper objectMapper = mapper.getIfAvailable();
99+
objectMapper = objectMapper == null ? new ObjectMapper() : objectMapper;
100+
101+
CurieProvider curieProvider = this.curieProvider.getIfAvailable();
102+
RelProvider relProvider = this.relProvider.getObject();
103+
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
104+
MessageSourceAccessor.class);
105+
106+
if (hypermediaTypes.contains(HypermediaType.HAL)) {
107+
converters.add(0, createHalConverter(objectMapper, curieProvider, relProvider, linkRelationMessageSource));
108+
}
109+
}
110+
111+
/**
112+
* @param objectMapper
113+
* @param curieProvider
114+
* @param relProvider
115+
* @param linkRelationMessageSource
116+
* @return
117+
*/
118+
private MappingJackson2HttpMessageConverter createHalConverter(ObjectMapper objectMapper, CurieProvider curieProvider,
119+
RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource) {
120+
121+
HalConfiguration halConfiguration = this.halConfiguration.getIfAvailable();
122+
halConfiguration = halConfiguration == null ? new HalConfiguration() : halConfiguration;
123+
124+
HalHandlerInstantiator instantiator = new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider,
125+
linkRelationMessageSource, halConfiguration);
126+
127+
ObjectMapper mapper = objectMapper.copy();
128+
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
129+
mapper.registerModule(new Jackson2HalModule());
130+
mapper.setHandlerInstantiator(instantiator);
131+
132+
MappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
133+
ResourceSupport.class);
134+
converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON, HAL_JSON_UTF8));
135+
converter.setObjectMapper(mapper);
136+
137+
return converter;
138+
}
139+
}

src/main/java/org/springframework/hateoas/config/EnableEntityLinks.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012 the original author or authors.
2+
* Copyright 2012-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -36,7 +36,9 @@
3636
@Target(ElementType.TYPE)
3737
@Inherited
3838
@Documented
39-
@Import(LinkBuilderBeanDefinitionRegistrar.class)
39+
@Import({ //
40+
EntityLinksConfiguration.class, //
41+
JaxRsConfigurationImportSelector.class //
42+
})
4043
public @interface EnableEntityLinks {
41-
4244
}

src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2013-2015 the original author or authors.
2+
* Copyright 2013-2018 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -39,11 +39,16 @@
3939
* @see LinkDiscoverer
4040
* @see EntityLinks
4141
* @author Oliver Gierke
42+
* @author Greg Turnquist
4243
*/
4344
@Retention(RetentionPolicy.RUNTIME)
4445
@Target(ElementType.TYPE)
4546
@Documented
46-
@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class })
47+
@EnableEntityLinks
48+
@Import({ //
49+
HypermediaSupportBeanDefinitionRegistrar.class, //
50+
HateoasConfiguration.class //
51+
})
4752
public @interface EnableHypermediaSupport {
4853

4954
/**
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2018 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+
package org.springframework.hateoas.config;
17+
18+
import org.springframework.context.annotation.Bean;
19+
import org.springframework.context.annotation.Configuration;
20+
import org.springframework.context.annotation.DependsOn;
21+
import org.springframework.context.annotation.Primary;
22+
import org.springframework.hateoas.EntityLinks;
23+
import org.springframework.hateoas.core.ControllerEntityLinksFactoryBean;
24+
import org.springframework.hateoas.core.DelegatingEntityLinks;
25+
import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory;
26+
import org.springframework.plugin.core.PluginRegistry;
27+
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
28+
import org.springframework.stereotype.Controller;
29+
30+
/**
31+
* Spring configuration to register a {@link PluginRegistry} for {@link EntityLinks}.
32+
*
33+
* @author Greg Turnquist
34+
* @author Oliver Gierke
35+
*/
36+
@Configuration
37+
class EntityLinksConfiguration {
38+
39+
@Bean
40+
PluginRegistryFactoryBean<EntityLinks, Class<?>> entityLinksPluginRegistry() {
41+
42+
PluginRegistryFactoryBean<EntityLinks, Class<?>> registry = new PluginRegistryFactoryBean<EntityLinks, Class<?>>();
43+
registry.setType(EntityLinks.class);
44+
registry.setExclusions(new Class[] { DelegatingEntityLinks.class });
45+
46+
return registry;
47+
}
48+
49+
@Primary
50+
@Bean
51+
@DependsOn("controllerEntityLinks")
52+
DelegatingEntityLinks delegatingEntityLinks(PluginRegistry<EntityLinks, Class<?>> entityLinksPluginRegistry) {
53+
return new DelegatingEntityLinks(entityLinksPluginRegistry);
54+
}
55+
56+
@Bean
57+
ControllerEntityLinksFactoryBean controllerEntityLinks(ControllerLinkBuilderFactory controllerLinkBuilderFactory) {
58+
59+
ControllerEntityLinksFactoryBean factory = new ControllerEntityLinksFactoryBean();
60+
factory.setAnnotation(Controller.class);
61+
factory.setLinkBuilderFactory(controllerLinkBuilderFactory);
62+
63+
return factory;
64+
}
65+
66+
@Bean
67+
ControllerLinkBuilderFactory controllerLinkBuilderFactoryBean() {
68+
return new ControllerLinkBuilderFactory();
69+
}
70+
}

0 commit comments

Comments
 (0)