#719 - 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.
This commit is contained in:
Oliver Gierke
2018-06-19 20:56:41 +02:00
parent 900b2e5572
commit 8cfbfc1b89
15 changed files with 528 additions and 838 deletions

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2017 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.hateoas.collectionjson;
import java.io.IOException;
import java.util.Arrays;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* A message converter that converts any object into a Collection+JSON document before bundling up as an
* {@link HttpOutputMessage}, or that converts any incoming {@link HttpInputMessage} into an object.
*
* @author Greg Turnquist
*/
public class CollectionJsonMessageConverter extends AbstractHttpMessageConverter<Object> {
private final ObjectMapper objectMapper;
public CollectionJsonMessageConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.objectMapper.registerModule(new Jackson2CollectionJsonModule());
setSupportedMediaTypes(Arrays.asList(MediaTypes.COLLECTION_JSON));
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.objectMapper.readValue(inputMessage.getBody(), clazz);
}
@Override
protected void writeInternal(Object t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(outputMessage.getBody(), JsonEncoding.UTF8);
// A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
jsonGenerator.useDefaultPrettyPrinter();
}
try {
objectMapper.writeValue(jsonGenerator, t);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
}

View File

@@ -1,42 +0,0 @@
/*
* Copyright 2017 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.hateoas.collectionjson;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Greg Turnquist
*/
@Configuration
public class CollectionJsonWebMvcConfigurer implements WebMvcConfigurer {
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#configureMessageConverters(java.util.List)
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new CollectionJsonMessageConverter(new ObjectMapper()));
}
}

View File

@@ -0,0 +1,55 @@
/*
* Copyright 2018 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.hateoas.config;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link BeanPostProcessor} to register {@link Jackson2HalModule} with {@link ObjectMapper} instances registered in the
* {@link ApplicationContext}.
*
* @author Oliver Gierke
*/
@RequiredArgsConstructor
class ConverterRegisteringBeanPostProcessor implements BeanPostProcessor {
private final ObjectFactory<ConverterRegisteringWebMvcConfigurer> configurer;
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RestTemplate) {
ConverterRegisteringWebMvcConfigurer object = configurer.getObject();
object.extendMessageConverters(((RestTemplate) bean).getMessageConverters());
}
return bean;
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2018 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.hateoas.config;
import static org.springframework.hateoas.MediaTypes.*;
import lombok.RequiredArgsConstructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.hateoas.hal.Jackson2HalModule.HalHandlerInstantiator;
import org.springframework.hateoas.hal.forms.HalFormsConfiguration;
import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule;
import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* @author Oliver Gierke
*/
@Configuration
@RequiredArgsConstructor
public class ConverterRegisteringWebMvcConfigurer implements WebMvcConfigurer, BeanFactoryAware {
private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource";
private final ObjectProvider<ObjectMapper> mapper;
private final ObjectProvider<DelegatingRelProvider> relProvider;
private final ObjectProvider<CurieProvider> curieProvider;
private final ObjectProvider<HalConfiguration> halConfiguration;
private final ObjectProvider<HalFormsConfiguration> halFormsConfiguration;
private BeanFactory beanFactory;
private Collection<HypermediaType> hypermediaTypes;
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* @param hyperMediaTypes the hyperMediaTypes to set
*/
public void setHypermediaTypes(Collection<HypermediaType> hyperMediaTypes) {
this.hypermediaTypes = hyperMediaTypes;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#extendMessageConverters(java.util.List)
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter halConverterCandidate = (MappingJackson2HttpMessageConverter) converter;
ObjectMapper objectMapper = halConverterCandidate.getObjectMapper();
if (Jackson2HalModule.isAlreadyRegisteredIn(objectMapper)) {
return;
}
}
}
ObjectMapper objectMapper = mapper.getIfAvailable(() -> new ObjectMapper());
CurieProvider curieProvider = this.curieProvider.getIfAvailable();
RelProvider relProvider = this.relProvider.getObject();
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
MessageSourceAccessor.class);
if (hypermediaTypes.contains(HypermediaType.HAL)) {
converters.add(0, createHalConverter(objectMapper, curieProvider, relProvider, linkRelationMessageSource));
}
if (hypermediaTypes.contains(HypermediaType.HAL_FORMS)) {
converters.add(0, createHalFormsConverter(objectMapper, curieProvider, relProvider, linkRelationMessageSource));
}
if (hypermediaTypes.contains(HypermediaType.COLLECTION_JSON)) {
converters.add(0, createCollectionJsonConverter(objectMapper, linkRelationMessageSource));
}
}
/**
* @param objectMapper
* @param linkRelationMessageSource
* @return
*/
protected MappingJackson2HttpMessageConverter createCollectionJsonConverter(ObjectMapper objectMapper,
MessageSourceAccessor linkRelationMessageSource) {
ObjectMapper collectionJsonObjectMapper = objectMapper.copy();
collectionJsonObjectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
collectionJsonObjectMapper.registerModule(new Jackson2CollectionJsonModule());
collectionJsonObjectMapper.setHandlerInstantiator(
new Jackson2CollectionJsonModule.CollectionJsonHandlerInstantiator(linkRelationMessageSource));
MappingJackson2HttpMessageConverter collectionJsonConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class);
collectionJsonConverter.setSupportedMediaTypes(Arrays.asList(COLLECTION_JSON));
collectionJsonConverter.setObjectMapper(collectionJsonObjectMapper);
return collectionJsonConverter;
}
/**
* @param objectMapper
* @param curieProvider
* @param relProvider
* @param linkRelationMessageSource
* @return
*/
private MappingJackson2HttpMessageConverter createHalFormsConverter(ObjectMapper objectMapper,
CurieProvider curieProvider, RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource) {
Jackson2HalFormsModule.HalFormsHandlerInstantiator hi = new Jackson2HalFormsModule.HalFormsHandlerInstantiator(
relProvider, curieProvider, linkRelationMessageSource, true,
this.halFormsConfiguration.getIfAvailable(() -> new HalFormsConfiguration()));
ObjectMapper mapper = objectMapper.copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2HalFormsModule());
mapper.setHandlerInstantiator(hi);
MappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class);
converter.setSupportedMediaTypes(Arrays.asList(HAL_FORMS_JSON));
converter.setObjectMapper(mapper);
return converter;
}
/**
* @param objectMapper
* @param curieProvider
* @param relProvider
* @param linkRelationMessageSource
* @return
*/
private MappingJackson2HttpMessageConverter createHalConverter(ObjectMapper objectMapper, CurieProvider curieProvider,
RelProvider relProvider, MessageSourceAccessor linkRelationMessageSource) {
HalConfiguration halConfiguration = this.halConfiguration.getIfAvailable(() -> new HalConfiguration());
HalHandlerInstantiator instantiator = new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider,
linkRelationMessageSource, halConfiguration);
ObjectMapper mapper = objectMapper.copy();
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
mapper.registerModule(new Jackson2HalModule());
mapper.setHandlerInstantiator(instantiator);
MappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class);
converter.setSupportedMediaTypes(Arrays.asList(HAL_JSON, HAL_JSON_UTF8));
converter.setObjectMapper(mapper);
return converter;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2018 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.
@@ -36,7 +36,6 @@ import org.springframework.hateoas.core.DelegatingEntityLinks;
@Target(ElementType.TYPE)
@Inherited
@Documented
@Import(LinkBuilderBeanDefinitionRegistrar.class)
@Import(EntityLinksConfiguration.class)
public @interface EnableEntityLinks {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2015 the original author or authors.
* Copyright 2013-2018 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.
@@ -15,26 +15,16 @@
*/
package org.springframework.hateoas.config;
import lombok.extern.slf4j.Slf4j;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.collectionjson.CollectionJsonWebMvcConfigurer;
import org.springframework.hateoas.hal.forms.HalFormsWebMvcConfigurer;
/**
* Activates hypermedia support in the {@link ApplicationContext}. Will register infrastructure beans available for
@@ -54,8 +44,8 @@ import org.springframework.hateoas.hal.forms.HalFormsWebMvcConfigurer;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class,
EnableHypermediaSupport.HypermediaConfigurationImportSelector.class })
@EnableEntityLinks
@Import({ HypermediaSupportBeanDefinitionRegistrar.class, HateoasConfiguration.class })
public @interface EnableHypermediaSupport {
/**
@@ -86,52 +76,13 @@ public @interface EnableHypermediaSupport {
*
* @see https://rwcbook.github.io/hal-forms/
*/
HAL_FORMS(HalFormsWebMvcConfigurer.class),
HAL_FORMS,
/**
* Collection+JSON
*
* @see http://amundsen.com/media-types/collection/format/
*/
COLLECTION_JSON(CollectionJsonWebMvcConfigurer.class);
private final List<Class<?>> configurations;
HypermediaType(Class<?>... configurations) {
this.configurations = Arrays.asList(configurations);
}
}
@Slf4j
class HypermediaConfigurationImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableHypermediaSupport.class.getName());
HypermediaType[] types = (HypermediaType[]) attributes.get("type");
/**
* If no types are defined inside the annotation, add them all.
*/
if (types.length == 0) {
types = HypermediaType.values();
}
LOG.debug("Registering support for hypermedia types {} according to configuration on {}", types,
metadata.getClassName());
List<String> configurationNames = new ArrayList<String>();
for (HypermediaType type : types) {
for (Class<?> configuration : type.configurations) {
configurationNames.add(configuration.getName());
}
}
return configurationNames.toArray(new String[0]);
}
COLLECTION_JSON;
}
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright 2018 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.hateoas.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.core.ControllerEntityLinksFactoryBean;
import org.springframework.hateoas.core.DelegatingEntityLinks;
import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
import org.springframework.stereotype.Controller;
/**
* Spring configuration to register a {@link PluginRegistry} for {@link EntityLinks}.
*
* @author Greg Turnquist
* @author Oliver Gierke
*/
@Configuration
class EntityLinksConfiguration {
@Bean
PluginRegistryFactoryBean<EntityLinks, Class<?>> entityLinksPluginRegistry() {
PluginRegistryFactoryBean<EntityLinks, Class<?>> registry = new PluginRegistryFactoryBean<>();
registry.setType(EntityLinks.class);
registry.setExclusions(new Class[] { DelegatingEntityLinks.class });
return registry;
}
@Primary
@Bean
@DependsOn("controllerEntityLinks")
DelegatingEntityLinks delegatingEntityLinks(PluginRegistry<EntityLinks, Class<?>> entityLinksPluginRegistry) {
return new DelegatingEntityLinks(entityLinksPluginRegistry);
}
@Bean
ControllerEntityLinksFactoryBean controllerEntityLinks(ControllerLinkBuilderFactory controllerLinkBuilderFactory) {
ControllerEntityLinksFactoryBean factory = new ControllerEntityLinksFactoryBean();
factory.setAnnotation(Controller.class);
factory.setLinkBuilderFactory(controllerLinkBuilderFactory);
return factory;
}
@Bean
ControllerLinkBuilderFactory controllerLinkBuilderFactoryBean() {
return new ControllerLinkBuilderFactory();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2015 the original author or authors.
* Copyright 2015-2018 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.
@@ -16,10 +16,24 @@
package org.springframework.hateoas.config;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.core.AnnotationRelProvider;
import org.springframework.hateoas.core.DefaultRelProvider;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.http.MediaType;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.plugin.core.config.EnablePluginRegistries;
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
import org.springframework.util.ClassUtils;
/**
* Common HATEOAS specific configuration.
@@ -29,6 +43,7 @@ import org.springframework.context.support.ReloadableResourceBundleMessageSource
* @since 0.19
*/
@Configuration
@EnablePluginRegistries({ LinkDiscoverer.class })
class HateoasConfiguration {
/**
@@ -50,4 +65,49 @@ class HateoasConfiguration {
throw new BeanCreationException("resourceDescriptionMessageSourceAccessor", "", o_O);
}
}
@Bean
ConverterRegisteringBeanPostProcessor jackson2ModuleRegisteringBeanPostProcessor(
ObjectFactory<ConverterRegisteringWebMvcConfigurer> configurer) {
return new ConverterRegisteringBeanPostProcessor(configurer);
}
// RelProvider
@Bean
RelProvider defaultRelProvider() {
return ClassUtils.isPresent("org.atteo.evo.inflector.English", null) //
? new EvoInflectorRelProvider()
: new DefaultRelProvider();
}
@Bean
AnnotationRelProvider annotationRelProvider() {
return new AnnotationRelProvider();
}
@Primary
@Bean
DelegatingRelProvider _relProvider(PluginRegistry<RelProvider, Class<?>> relProviderPluginRegistry) {
return new DelegatingRelProvider(relProviderPluginRegistry);
}
@Bean
PluginRegistryFactoryBean<RelProvider, Class<?>> relProviderPluginRegistry() {
PluginRegistryFactoryBean<RelProvider, Class<?>> factory = new PluginRegistryFactoryBean<>();
factory.setType(RelProvider.class);
factory.setExclusions(new Class[] { DelegatingRelProvider.class });
return factory;
}
// LinkDiscoverers
@Bean
LinkDiscoverers linkDiscoverers(PluginRegistry<LinkDiscoverer, MediaType> discoverers) {
return new LinkDiscoverers(discoverers);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2016 the original author or authors.
* Copyright 2013-2018 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.
@@ -17,64 +17,27 @@ package org.springframework.hateoas.config;
import static org.springframework.beans.factory.support.BeanDefinitionBuilder.*;
import static org.springframework.beans.factory.support.BeanDefinitionReaderUtils.*;
import static org.springframework.hateoas.MediaTypes.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.LinkDiscoverer;
import org.springframework.hateoas.LinkDiscoverers;
import org.springframework.hateoas.RelProvider;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.collectionjson.CollectionJsonLinkDiscoverer;
import org.springframework.hateoas.collectionjson.Jackson2CollectionJsonModule;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.hateoas.core.AnnotationRelProvider;
import org.springframework.hateoas.core.DefaultRelProvider;
import org.springframework.hateoas.core.DelegatingRelProvider;
import org.springframework.hateoas.core.EvoInflectorRelProvider;
import org.springframework.hateoas.hal.CurieProvider;
import org.springframework.hateoas.hal.HalConfiguration;
import org.springframework.hateoas.hal.HalLinkDiscoverer;
import org.springframework.hateoas.hal.Jackson2HalModule;
import org.springframework.hateoas.hal.forms.HalFormsConfiguration;
import org.springframework.hateoas.hal.forms.HalFormsLinkDiscoverer;
import org.springframework.hateoas.hal.forms.Jackson2HalFormsModule;
import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* {@link ImportBeanDefinitionRegistrar} implementation to activate hypermedia support based on the configured
@@ -84,23 +47,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
* @author Oliver Gierke
* @author Greg Turnquist
*/
class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
private static final String DELEGATING_REL_PROVIDER_BEAN_NAME = "_relProvider";
private static final String LINK_DISCOVERER_REGISTRY_BEAN_NAME = "_linkDiscovererRegistry";
private static final String HAL_OBJECT_MAPPER_BEAN_NAME = "_halObjectMapper";
private static final String HAL_FORMS_OBJECT_MAPPER_BEAN_NAME = "_halFormsObjectMapper";
private static final String COLLECTION_JSON_OBJECT_MAPPER_BEAN_NAME = "_collectionJsonObjectMapper";
private static final String MESSAGE_SOURCE_BEAN_NAME = "linkRelationMessageSource";
private static final boolean JACKSON2_PRESENT = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
null);
private static final boolean JSONPATH_PRESENT = ClassUtils.isPresent("com.jayway.jsonpath.JsonPath", null);
private static final boolean EVO_PRESENT = ClassUtils.isPresent("org.atteo.evo.inflector.English", null);
private final ImportBeanDefinitionRegistrar linkBuilderBeanDefinitionRegistrar = new LinkBuilderBeanDefinitionRegistrar();
private ListableBeanFactory beanFactory;
/*
* (non-Javadoc)
@@ -109,14 +58,12 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
linkBuilderBeanDefinitionRegistrar.registerBeanDefinitions(metadata, registry);
Map<String, Object> attributes = metadata.getAnnotationAttributes(EnableHypermediaSupport.class.getName());
Collection<HypermediaType> types = Arrays.asList((HypermediaType[]) attributes.get("type"));
for (HypermediaType type : types) {
if (JSONPATH_PRESENT) {
if (JSONPATH_PRESENT) {
for (HypermediaType type : types) {
AbstractBeanDefinition linkDiscovererBeanDefinition = getLinkDiscovererBeanDefinition(type);
registerBeanDefinition(new BeanDefinitionHolder(linkDiscovererBeanDefinition,
@@ -124,84 +71,9 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
}
}
if (types.contains(HypermediaType.HAL)) {
registerHypermediaComponents(metadata, registry, HAL_OBJECT_MAPPER_BEAN_NAME);
}
if (types.contains(HypermediaType.HAL_FORMS)) {
registerHypermediaComponents(metadata, registry, HAL_FORMS_OBJECT_MAPPER_BEAN_NAME);
}
if (types.contains(HypermediaType.COLLECTION_JSON)) {
registerHypermediaComponents(metadata, registry, COLLECTION_JSON_OBJECT_MAPPER_BEAN_NAME);
}
if (!types.isEmpty()) {
BeanDefinitionBuilder linkDiscoverersRegistryBuilder = BeanDefinitionBuilder
.rootBeanDefinition(PluginRegistryFactoryBean.class);
linkDiscoverersRegistryBuilder.addPropertyValue("type", LinkDiscoverer.class);
registerSourcedBeanDefinition(linkDiscoverersRegistryBuilder, metadata, registry,
LINK_DISCOVERER_REGISTRY_BEAN_NAME);
BeanDefinitionBuilder linkDiscoverersBuilder = BeanDefinitionBuilder.rootBeanDefinition(LinkDiscoverers.class);
linkDiscoverersBuilder.addConstructorArgReference(LINK_DISCOVERER_REGISTRY_BEAN_NAME);
registerSourcedBeanDefinition(linkDiscoverersBuilder, metadata, registry);
}
registerRelProviderPluginRegistryAndDelegate(registry);
}
private static void registerHypermediaComponents(AnnotationMetadata metadata, BeanDefinitionRegistry registry,
String objectMapperBeanName) {
if (JACKSON2_PRESENT) {
BeanDefinitionBuilder queryMapperBuilder = rootBeanDefinition(ObjectMapper.class);
registerSourcedBeanDefinition(queryMapperBuilder, metadata, registry, objectMapperBeanName);
BeanDefinitionBuilder customizerBeanDefinition = rootBeanDefinition(DefaultObjectMapperCustomizer.class);
registerSourcedBeanDefinition(customizerBeanDefinition, metadata, registry);
BeanDefinitionBuilder builder = rootBeanDefinition(Jackson2ModuleRegisteringBeanPostProcessor.class);
registerSourcedBeanDefinition(builder, metadata, registry);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ListableBeanFactory) beanFactory;
}
/**
* Registers bean definitions for a {@link PluginRegistry} to capture {@link RelProvider} instances. Wraps the
* registry into a {@link DelegatingRelProvider} bean definition backed by the registry.
*
* @param registry
*/
private static void registerRelProviderPluginRegistryAndDelegate(BeanDefinitionRegistry registry) {
Class<?> defaultRelProviderType = EVO_PRESENT ? EvoInflectorRelProvider.class : DefaultRelProvider.class;
RootBeanDefinition defaultRelProviderBeanDefinition = new RootBeanDefinition(defaultRelProviderType);
registry.registerBeanDefinition("defaultRelProvider", defaultRelProviderBeanDefinition);
RootBeanDefinition annotationRelProviderBeanDefinition = new RootBeanDefinition(AnnotationRelProvider.class);
registry.registerBeanDefinition("annotationRelProvider", annotationRelProviderBeanDefinition);
BeanDefinitionBuilder registryFactoryBeanBuilder = BeanDefinitionBuilder
.rootBeanDefinition(PluginRegistryFactoryBean.class);
registryFactoryBeanBuilder.addPropertyValue("type", RelProvider.class);
registryFactoryBeanBuilder.addPropertyValue("exclusions", DelegatingRelProvider.class);
AbstractBeanDefinition registryBeanDefinition = registryFactoryBeanBuilder.getBeanDefinition();
registry.registerBeanDefinition("relProviderPluginRegistry", registryBeanDefinition);
BeanDefinitionBuilder delegateBuilder = BeanDefinitionBuilder.rootBeanDefinition(DelegatingRelProvider.class);
delegateBuilder.addConstructorArgValue(registryBeanDefinition);
AbstractBeanDefinition beanDefinition = delegateBuilder.getBeanDefinition();
beanDefinition.setPrimary(true);
registry.registerBeanDefinition(DELEGATING_REL_PROVIDER_BEAN_NAME, beanDefinition);
BeanDefinitionBuilder configurerBeanDefinition = rootBeanDefinition(ConverterRegisteringWebMvcConfigurer.class);
configurerBeanDefinition.addPropertyValue("hypermediaTypes", types);
registerSourcedBeanDefinition(configurerBeanDefinition, metadata, registry);
}
/**
@@ -250,190 +122,4 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe
registerBeanDefinition(holder, registry);
return name;
}
/**
* {@link BeanPostProcessor} to register {@link Jackson2HalModule} with {@link ObjectMapper} instances registered in
* the {@link ApplicationContext}.
*
* @author Oliver Gierke
*/
static class Jackson2ModuleRegisteringBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
private AutowireCapableBeanFactory beanFactory;
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
*/
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
Assert.isInstanceOf(AutowireCapableBeanFactory.class, beanFactory,
"BeanFactory must be an AutowireCapableBeanFactory!");
this.beanFactory = (AutowireCapableBeanFactory) beanFactory;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof RequestMappingHandlerAdapter) {
RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
adapter.setMessageConverters(potentiallyRegisterModule(adapter.getMessageConverters()));
}
if (bean instanceof RestTemplate) {
RestTemplate template = (RestTemplate) bean;
template.setMessageConverters(potentiallyRegisterModule(template.getMessageConverters()));
}
return bean;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private List<HttpMessageConverter<?>> potentiallyRegisterModule(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter<?> converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
MappingJackson2HttpMessageConverter halConverterCandidate = (MappingJackson2HttpMessageConverter) converter;
ObjectMapper objectMapper = halConverterCandidate.getObjectMapper();
if (Jackson2HalModule.isAlreadyRegisteredIn(objectMapper)) {
return converters;
}
}
}
CurieProvider curieProvider = getCurieProvider(beanFactory);
RelProvider relProvider = beanFactory.getBean(DELEGATING_REL_PROVIDER_BEAN_NAME, RelProvider.class);
List<HttpMessageConverter<?>> result = new ArrayList<HttpMessageConverter<?>>(converters.size());
if (beanFactory.containsBean(HAL_OBJECT_MAPPER_BEAN_NAME)) {
ObjectMapper halObjectMapper = beanFactory.getBean(HAL_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
MessageSourceAccessor.class);
halObjectMapper.registerModule(new Jackson2HalModule());
try {
HalConfiguration halConfiguration = beanFactory.getBean(HalConfiguration.class);
halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider,
linkRelationMessageSource, halConfiguration));
} catch (BeansException e) {
halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider,
linkRelationMessageSource, new HalConfiguration()));
}
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class);
halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON, HAL_JSON_UTF8));
halConverter.setObjectMapper(halObjectMapper);
result.add(halConverter);
}
if (beanFactory.containsBean(HAL_FORMS_OBJECT_MAPPER_BEAN_NAME)) {
ObjectMapper halFormsObjectMapper = beanFactory.getBean(HAL_FORMS_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
MessageSourceAccessor.class);
halFormsObjectMapper.registerModule(new Jackson2HalFormsModule());
try {
HalFormsConfiguration halFormsConfiguration = beanFactory.getBean(HalFormsConfiguration.class);
halFormsObjectMapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider, curieProvider,
linkRelationMessageSource, true, halFormsConfiguration));
} catch (BeansException e) {
halFormsObjectMapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider, curieProvider,
linkRelationMessageSource, true, new HalFormsConfiguration()));
}
MappingJackson2HttpMessageConverter halFormsConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class);
halFormsConverter.setSupportedMediaTypes(Arrays.asList(HAL_FORMS_JSON));
halFormsConverter.setObjectMapper(halFormsObjectMapper);
result.add(halFormsConverter);
}
if (beanFactory.containsBean(COLLECTION_JSON_OBJECT_MAPPER_BEAN_NAME)) {
ObjectMapper collectionJsonObjectMapper = beanFactory.getBean(COLLECTION_JSON_OBJECT_MAPPER_BEAN_NAME, ObjectMapper.class);
MessageSourceAccessor linkRelationMessageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME,
MessageSourceAccessor.class);
collectionJsonObjectMapper.registerModule(new Jackson2CollectionJsonModule());
collectionJsonObjectMapper.setHandlerInstantiator(
new Jackson2CollectionJsonModule.CollectionJsonHandlerInstantiator(linkRelationMessageSource));
MappingJackson2HttpMessageConverter jsonCollectionConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(
ResourceSupport.class);
jsonCollectionConverter.setSupportedMediaTypes(Arrays.asList(COLLECTION_JSON));
jsonCollectionConverter.setObjectMapper(collectionJsonObjectMapper);
result.add(jsonCollectionConverter);
}
result.addAll(converters);
return result;
}
private static CurieProvider getCurieProvider(BeanFactory factory) {
try {
return factory.getBean(CurieProvider.class);
} catch (NoSuchBeanDefinitionException e) {
return null;
}
}
}
/**
* {@link BeanPostProcessor} to disable the default HAL {@link ObjectMapper} to fail on unknown properties. Needed as
* the methods to do that on {@link Jackson2ObjectMapperFactoryBean} were introduced in Spring 4.1 only.
*
* @author Oliver Gierke
*/
private static class DefaultObjectMapperCustomizer implements BeanPostProcessor {
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (HAL_OBJECT_MAPPER_BEAN_NAME.equals(beanName) || HAL_FORMS_OBJECT_MAPPER_BEAN_NAME.equals(beanName) || COLLECTION_JSON_OBJECT_MAPPER_BEAN_NAME.equals(beanName)) {
ObjectMapper mapper = (ObjectMapper) bean;
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
return mapper;
}
return bean;
}
/*
* (non-Javadoc)
* @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright 2012-2013 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.hateoas.config;
import java.lang.annotation.Annotation;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.LinkBuilderFactory;
import org.springframework.hateoas.core.ControllerEntityLinksFactoryBean;
import org.springframework.hateoas.core.DelegatingEntityLinks;
import org.springframework.hateoas.mvc.ControllerLinkBuilderFactory;
import org.springframework.plugin.core.support.PluginRegistryFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.util.ClassUtils;
/**
* {@link ImportBeanDefinitionRegistrar} to register a {@link DelegatingEntityLinks} instance as well as a
* {@link ControllerEntityLinksFactoryBean} for Spring MVC controllers and JAX-RS resources if present.
*
* @author Oliver Gierke
*/
class LinkBuilderBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
private static final boolean IS_JAX_RS_PRESENT = ClassUtils.isPresent("javax.ws.rs.Path",
ClassUtils.getDefaultClassLoader());
/*
* (non-Javadoc)
* @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar#registerBeanDefinitions(org.springframework.core.type.AnnotationMetadata, org.springframework.beans.factory.support.BeanDefinitionRegistry)
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
BeanDefinitionBuilder registryFactoryBeanBuilder = BeanDefinitionBuilder
.rootBeanDefinition(PluginRegistryFactoryBean.class);
registryFactoryBeanBuilder.addPropertyValue("type", EntityLinks.class);
registryFactoryBeanBuilder.addPropertyValue("exclusions", DelegatingEntityLinks.class);
AbstractBeanDefinition registryBeanDefinition = registryFactoryBeanBuilder.getBeanDefinition();
registry.registerBeanDefinition("entityLinksPluginRegistry", registryBeanDefinition);
BeanDefinitionBuilder delegateBuilder = BeanDefinitionBuilder.rootBeanDefinition(DelegatingEntityLinks.class);
delegateBuilder.addConstructorArgValue(registryBeanDefinition);
BeanDefinitionBuilder builder = getEntityControllerLinksFor(Controller.class, ControllerLinkBuilderFactory.class);
registry.registerBeanDefinition("controllerEntityLinks", builder.getBeanDefinition());
delegateBuilder.addDependsOn("controllerEntityLinks");
AbstractBeanDefinition beanDefinition = delegateBuilder.getBeanDefinition();
beanDefinition.setPrimary(true);
registry.registerBeanDefinition("delegatingEntityLinks", beanDefinition);
}
private static BeanDefinitionBuilder getEntityControllerLinksFor(Class<? extends Annotation> type,
Class<? extends LinkBuilderFactory<?>> linkBuilderFactoryType) {
RootBeanDefinition definition = new RootBeanDefinition(linkBuilderFactoryType);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ControllerEntityLinksFactoryBean.class);
builder.addPropertyValue("annotation", type);
builder.addPropertyValue("linkBuilderFactory", definition);
return builder;
}
}

View File

@@ -1,91 +0,0 @@
/*
* Copyright 2016-2017 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.hateoas.hal.forms;
import java.io.IOException;
import java.util.Arrays;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
/**
* A message converter that converts any object into a HAL-FORMS document before bundling up as an
* {@link HttpOutputMessage}, or that converts any incoming {@link HttpInputMessage} into an object.
*
* @author Dietrich Schulten
* @author Greg Turnquist
*/
class HalFormsMessageConverter extends AbstractHttpMessageConverter<Object> {
private final ObjectMapper objectMapper;
public HalFormsMessageConverter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
this.objectMapper.registerModule(new Jackson2HalFormsModule());
setSupportedMediaTypes(Arrays.asList(MediaTypes.HAL_FORMS_JSON));
}
/*
* (non-Javadoc)
* @see org.springframework.http.converter.AbstractHttpMessageConverter#supports(java.lang.Class)
*/
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
/*
* (non-Javadoc)
* @see org.springframework.http.converter.AbstractHttpMessageConverter#readInternal(java.lang.Class, org.springframework.http.HttpInputMessage)
*/
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
return this.objectMapper.readValue(inputMessage.getBody(), clazz);
}
@Override
protected void writeInternal(Object t, HttpOutputMessage outputMessage)
throws IOException, HttpMessageNotWritableException {
JsonGenerator jsonGenerator = objectMapper.getFactory().createGenerator(outputMessage.getBody(), JsonEncoding.UTF8);
// A workaround for JsonGenerators not applying serialization features
// https://github.com/FasterXML/jackson-databind/issues/12
if (objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
jsonGenerator.useDefaultPrettyPrinter();
}
try {
objectMapper.writeValue(jsonGenerator, t);
} catch (JsonProcessingException ex) {
throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
}
}
}

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2016-2017 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.hateoas.hal.forms;
import java.util.List;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Configure a HAL-FORMS {@link HttpMessageConverter}.
*
* @author Oliver Gierke
* @author Greg Turnquist
*/
@Configuration
public class HalFormsWebMvcConfigurer implements WebMvcConfigurer {
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#configureMessageConverters(java.util.List)
*/
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new HalFormsMessageConverter(new ObjectMapper()));
}
}