diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonMessageConverter.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonMessageConverter.java deleted file mode 100644 index ae73b37a..00000000 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonMessageConverter.java +++ /dev/null @@ -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 { - - 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); - } - - } -} diff --git a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonWebMvcConfigurer.java b/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonWebMvcConfigurer.java deleted file mode 100644 index c54ba5ad..00000000 --- a/src/main/java/org/springframework/hateoas/collectionjson/CollectionJsonWebMvcConfigurer.java +++ /dev/null @@ -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> converters) { - converters.add(new CollectionJsonMessageConverter(new ObjectMapper())); - } - - -} diff --git a/src/main/java/org/springframework/hateoas/config/ConverterRegisteringBeanPostProcessor.java b/src/main/java/org/springframework/hateoas/config/ConverterRegisteringBeanPostProcessor.java new file mode 100644 index 00000000..f8e4aa28 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/config/ConverterRegisteringBeanPostProcessor.java @@ -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 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; + } +} diff --git a/src/main/java/org/springframework/hateoas/config/ConverterRegisteringWebMvcConfigurer.java b/src/main/java/org/springframework/hateoas/config/ConverterRegisteringWebMvcConfigurer.java new file mode 100644 index 00000000..22c290d9 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/config/ConverterRegisteringWebMvcConfigurer.java @@ -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 mapper; + private final ObjectProvider relProvider; + private final ObjectProvider curieProvider; + private final ObjectProvider halConfiguration; + private final ObjectProvider halFormsConfiguration; + + private BeanFactory beanFactory; + private Collection 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 hyperMediaTypes) { + this.hypermediaTypes = hyperMediaTypes; + } + + /* + * (non-Javadoc) + * @see org.springframework.web.servlet.config.annotation.WebMvcConfigurer#extendMessageConverters(java.util.List) + */ + @Override + public void extendMessageConverters(List> 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; + } +} diff --git a/src/main/java/org/springframework/hateoas/config/EnableEntityLinks.java b/src/main/java/org/springframework/hateoas/config/EnableEntityLinks.java index cedd6f55..2f8ee029 100644 --- a/src/main/java/org/springframework/hateoas/config/EnableEntityLinks.java +++ b/src/main/java/org/springframework/hateoas/config/EnableEntityLinks.java @@ -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 { - } diff --git a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java index 976525f5..1fc5e5a6 100644 --- a/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java +++ b/src/main/java/org/springframework/hateoas/config/EnableHypermediaSupport.java @@ -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> configurations; - - HypermediaType(Class... configurations) { - this.configurations = Arrays.asList(configurations); - } - } - - @Slf4j - class HypermediaConfigurationImportSelector implements ImportSelector { - - @Override - public String[] selectImports(AnnotationMetadata metadata) { - - Map 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 configurationNames = new ArrayList(); - - for (HypermediaType type : types) { - for (Class configuration : type.configurations) { - configurationNames.add(configuration.getName()); - } - } - - return configurationNames.toArray(new String[0]); - } + COLLECTION_JSON; } } diff --git a/src/main/java/org/springframework/hateoas/config/EntityLinksConfiguration.java b/src/main/java/org/springframework/hateoas/config/EntityLinksConfiguration.java new file mode 100644 index 00000000..3e61f525 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/config/EntityLinksConfiguration.java @@ -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> entityLinksPluginRegistry() { + + PluginRegistryFactoryBean> registry = new PluginRegistryFactoryBean<>(); + registry.setType(EntityLinks.class); + registry.setExclusions(new Class[] { DelegatingEntityLinks.class }); + + return registry; + } + + @Primary + @Bean + @DependsOn("controllerEntityLinks") + DelegatingEntityLinks delegatingEntityLinks(PluginRegistry> 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(); + } +} diff --git a/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java index 3e942116..9b312289 100644 --- a/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java +++ b/src/main/java/org/springframework/hateoas/config/HateoasConfiguration.java @@ -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 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> relProviderPluginRegistry) { + return new DelegatingRelProvider(relProviderPluginRegistry); + } + + @Bean + PluginRegistryFactoryBean> relProviderPluginRegistry() { + + PluginRegistryFactoryBean> factory = new PluginRegistryFactoryBean<>(); + + factory.setType(RelProvider.class); + factory.setExclusions(new Class[] { DelegatingRelProvider.class }); + + return factory; + } + + // LinkDiscoverers + + @Bean + LinkDiscoverers linkDiscoverers(PluginRegistry discoverers) { + return new LinkDiscoverers(discoverers); + } } diff --git a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java index e9616266..bfbd6b73 100644 --- a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java +++ b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java @@ -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 attributes = metadata.getAnnotationAttributes(EnableHypermediaSupport.class.getName()); Collection 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> potentiallyRegisterModule(List> 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> result = new ArrayList>(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; - } - } } diff --git a/src/main/java/org/springframework/hateoas/config/LinkBuilderBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/LinkBuilderBeanDefinitionRegistrar.java deleted file mode 100644 index 5a9c02fa..00000000 --- a/src/main/java/org/springframework/hateoas/config/LinkBuilderBeanDefinitionRegistrar.java +++ /dev/null @@ -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 type, - Class> 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; - } -} diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverter.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverter.java deleted file mode 100644 index f0c03a60..00000000 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverter.java +++ /dev/null @@ -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 { - - 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 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); - } - } -} diff --git a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsWebMvcConfigurer.java b/src/main/java/org/springframework/hateoas/hal/forms/HalFormsWebMvcConfigurer.java deleted file mode 100644 index 699fdf47..00000000 --- a/src/main/java/org/springframework/hateoas/hal/forms/HalFormsWebMvcConfigurer.java +++ /dev/null @@ -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> converters) { - converters.add(new HalFormsMessageConverter(new ObjectMapper())); - } -} diff --git a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java index c683b312..908984f8 100755 --- a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java @@ -20,12 +20,13 @@ import static org.springframework.hateoas.hal.HalConfiguration.RenderSingleLinks import java.lang.reflect.Method; import java.util.List; +import java.util.Optional; +import java.util.function.Function; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -38,7 +39,6 @@ import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.collectionjson.CollectionJsonLinkDiscoverer; import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; -import org.springframework.hateoas.config.HypermediaSupportBeanDefinitionRegistrar.Jackson2ModuleRegisteringBeanPostProcessor; import org.springframework.hateoas.core.DelegatingEntityLinks; import org.springframework.hateoas.core.DelegatingRelProvider; import org.springframework.hateoas.hal.HalConfiguration; @@ -46,13 +46,19 @@ import org.springframework.hateoas.hal.HalLinkDiscoverer; import org.springframework.hateoas.hal.forms.HalFormsConfiguration; import org.springframework.hateoas.hal.forms.HalFormsLinkDiscoverer; import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; +import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.mock.web.MockServletContext; import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.MockMvc; import org.springframework.util.ReflectionUtils; import org.springframework.web.client.RestTemplate; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; @@ -69,6 +75,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; @RunWith(MockitoJUnitRunner.class) public class EnableHypermediaSupportIntegrationTest { + MockMvc mockMvc; + @Test public void bootstrapHalConfiguration() { assertHalSetupForConfigClass(HalConfig.class); @@ -105,7 +113,8 @@ public class EnableHypermediaSupportIntegrationTest { LinkDiscoverers discoverers = context.getBean(LinkDiscoverers.class); assertThat(discoverers).isNotNull(); - assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_FORMS_JSON)).isInstanceOf(HalFormsLinkDiscoverer.class); + assertThat(discoverers.getLinkDiscovererFor(MediaTypes.HAL_FORMS_JSON)) + .isInstanceOf(HalFormsLinkDiscoverer.class); assertRelProvidersSetUp(context); }); } @@ -118,7 +127,8 @@ public class EnableHypermediaSupportIntegrationTest { LinkDiscoverers discoverers = context.getBean(LinkDiscoverers.class); assertThat(discoverers).isNotNull(); - assertThat(discoverers.getLinkDiscovererFor(MediaTypes.COLLECTION_JSON)).isInstanceOf(CollectionJsonLinkDiscoverer.class); + assertThat(discoverers.getLinkDiscovererFor(MediaTypes.COLLECTION_JSON)) + .isInstanceOf(CollectionJsonLinkDiscoverer.class); assertRelProvidersSetUp(context); }); } @@ -147,9 +157,6 @@ public class EnableHypermediaSupportIntegrationTest { withContext(HalConfig.class, context -> { - Jackson2ModuleRegisteringBeanPostProcessor postProcessor = new HypermediaSupportBeanDefinitionRegistrar.Jackson2ModuleRegisteringBeanPostProcessor(); - postProcessor.setBeanFactory(context.getAutowireCapableBeanFactory()); - RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes()) // @@ -182,14 +189,10 @@ public class EnableHypermediaSupportIntegrationTest { withContext(HalFormsConfig.class, context -> { - Jackson2ModuleRegisteringBeanPostProcessor postProcessor = new HypermediaSupportBeanDefinitionRegistrar.Jackson2ModuleRegisteringBeanPostProcessor(); - postProcessor.setBeanFactory(context.getAutowireCapableBeanFactory()); - RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); - assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes()) - .hasSize(1) - .contains(MediaTypes.HAL_FORMS_JSON); + assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes()).hasSize(1) + .contains(MediaTypes.HAL_FORMS_JSON); boolean found = false; @@ -201,12 +204,10 @@ public class EnableHypermediaSupportIntegrationTest { AbstractMessageConverterMethodArgumentResolver processor = (AbstractMessageConverterMethodArgumentResolver) resolver; List> converters = (List>) ReflectionTestUtils - .getField(processor, "messageConverters"); + .getField(processor, "messageConverters"); assertThat(converters.get(0)).isInstanceOf(TypeConstrainedMappingJackson2HttpMessageConverter.class); - assertThat(converters.get(0).getSupportedMediaTypes()) - .hasSize(1) - .contains(MediaTypes.HAL_FORMS_JSON); + assertThat(converters.get(0).getSupportedMediaTypes()).hasSize(1).contains(MediaTypes.HAL_FORMS_JSON); } } @@ -220,14 +221,10 @@ public class EnableHypermediaSupportIntegrationTest { withContext(CollectionJsonConfig.class, context -> { - Jackson2ModuleRegisteringBeanPostProcessor postProcessor = new HypermediaSupportBeanDefinitionRegistrar.Jackson2ModuleRegisteringBeanPostProcessor(); - postProcessor.setBeanFactory(context.getAutowireCapableBeanFactory()); - RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); - assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes()) - .hasSize(1) - .contains(MediaTypes.COLLECTION_JSON); + assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes()).hasSize(1) + .contains(MediaTypes.COLLECTION_JSON); boolean found = false; @@ -239,12 +236,10 @@ public class EnableHypermediaSupportIntegrationTest { AbstractMessageConverterMethodArgumentResolver processor = (AbstractMessageConverterMethodArgumentResolver) resolver; List> converters = (List>) ReflectionTestUtils - .getField(processor, "messageConverters"); + .getField(processor, "messageConverters"); assertThat(converters.get(0)).isInstanceOf(TypeConstrainedMappingJackson2HttpMessageConverter.class); - assertThat(converters.get(0).getSupportedMediaTypes()) - .hasSize(1) - .contains(MediaTypes.COLLECTION_JSON); + assertThat(converters.get(0).getSupportedMediaTypes()).hasSize(1).contains(MediaTypes.COLLECTION_JSON); } } @@ -271,23 +266,60 @@ public class EnableHypermediaSupportIntegrationTest { public void registersHalFormsHttpMessageConvertersForRestTemplate() { withContext(HalFormsConfig.class, context -> { - RestTemplate template = context.getBean(RestTemplate.class); - assertThat(template.getMessageConverters().get(0).getSupportedMediaTypes()) - .hasSize(1) - .contains(MediaTypes.HAL_FORMS_JSON); + foo(context, RestTemplate.class, it -> it.getMessageConverters().get(0), converter -> { + + assertThat(converter.getSupportedMediaTypes()) // + .hasSize(1).contains(MediaTypes.HAL_FORMS_JSON); + }); }); } + private static void foo(ApplicationContext context, Class beanType, Function extractor, + ThrowingConsumer consumer) { + + T bean = context.getBean(beanType); + S result = extractor.apply(bean); + + try { + consumer.accept(result); + } catch (Throwable o_O) { + throw new RuntimeException(o_O); + } + } + + private static void assertObjectMapper(ApplicationContext context, MediaType mediaType, + ThrowingConsumer consumer) { + + Function mapper = adapter -> { + + Optional result = adapter.getMessageConverters().stream()// + .filter(it -> it.getSupportedMediaTypes().contains(mediaType)).findFirst() // + .map(AbstractJackson2HttpMessageConverter.class::cast) // + .map(it -> it.getObjectMapper()); + + if (!result.isPresent()) { + fail("Couldn't find ObjectMapper from HttpMessageConverter supporting " + mediaType); + } + + return result.orElseThrow(IllegalStateException::new); + }; + + foo(context, RequestMappingHandlerAdapter.class, mapper, consumer); + } + + interface ThrowingConsumer { + void accept(T source) throws Throwable; + } + @Test public void registersCollectionJsonHttpMessageConvertersForRestTemplate() { withContext(CollectionJsonConfig.class, context -> { RestTemplate template = context.getBean(RestTemplate.class); - assertThat(template.getMessageConverters().get(0).getSupportedMediaTypes()) - .hasSize(1) - .contains(MediaTypes.COLLECTION_JSON); + assertThat(template.getMessageConverters().get(0).getSupportedMediaTypes()).hasSize(1) + .contains(MediaTypes.COLLECTION_JSON); }); } @@ -299,9 +331,9 @@ public class EnableHypermediaSupportIntegrationTest { withContext(HalConfig.class, context -> { - ObjectMapper mapper = context.getBean("_halObjectMapper", ObjectMapper.class); - - assertThat(mapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + assertObjectMapper(context, MediaTypes.HAL_JSON, mapper -> { + assertThat(mapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + }); }); } @@ -313,9 +345,9 @@ public class EnableHypermediaSupportIntegrationTest { withContext(HalFormsConfig.class, context -> { - ObjectMapper mapper = context.getBean("_halFormsObjectMapper", ObjectMapper.class); - - assertThat(mapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + assertObjectMapper(context, MediaTypes.HAL_FORMS_JSON, mapper -> { + assertThat(mapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + }); }); } @@ -324,26 +356,36 @@ public class EnableHypermediaSupportIntegrationTest { withContext(CollectionJsonConfig.class, context -> { - ObjectMapper mapper = context.getBean("_collectionJsonObjectMapper", ObjectMapper.class); - - assertThat(mapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + assertObjectMapper(context, MediaTypes.COLLECTION_JSON, mapper -> { + assertThat(mapper.isEnabled(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)).isFalse(); + }); }); } @Test public void verifyDefaultHalConfigurationRendersSingleItemAsSingleItem() throws JsonProcessingException { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(HalConfig.class); + withContext(HalConfig.class, context -> { - ObjectMapper mapper = context.getBean("_halObjectMapper", ObjectMapper.class); + RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); - ResourceSupport resourceSupport = new ResourceSupport(); - resourceSupport.add(new Link("localhost").withSelfRel()); + Optional mapper = adapter.getMessageConverters().stream() // + .filter(it -> it.getSupportedMediaTypes().contains(MediaType.parseMediaType("application/hal+json"))) // + .findFirst() // + .map(AbstractJackson2HttpMessageConverter.class::cast) // + .map(AbstractJackson2HttpMessageConverter::getObjectMapper); - assertThat(mapper.writeValueAsString(resourceSupport)) - .isEqualTo("{\"_links\":{\"self\":{\"href\":\"localhost\"}}}"); + assertThat(mapper).hasValueSatisfying(it -> { - context.close(); + ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(new Link("localhost").withSelfRel()); + + assertThatCode(() -> { // + assertThat(it.writeValueAsString(resourceSupport)) // + .isEqualTo("{\"_links\":{\"self\":{\"href\":\"localhost\"}}}"); + }); + }); + }); } @Test @@ -351,20 +393,27 @@ public class EnableHypermediaSupportIntegrationTest { withContext(RenderLinkAsSingleLinksConfig.class, context -> { - ObjectMapper mapper = context.getBean("_halObjectMapper", ObjectMapper.class); + assertObjectMapper(context, MediaTypes.HAL_JSON, mapper -> { - ResourceSupport resourceSupport = new ResourceSupport(); - resourceSupport.add(new Link("localhost").withSelfRel()); + ResourceSupport resourceSupport = new ResourceSupport(); + resourceSupport.add(new Link("localhost").withSelfRel()); + + assertThat(mapper.writeValueAsString(resourceSupport)) + .isEqualTo("{\"_links\":{\"self\":[{\"href\":\"localhost\"}]}}"); + }); - assertThat(mapper.writeValueAsString(resourceSupport)) - .isEqualTo("{\"_links\":{\"self\":[{\"href\":\"localhost\"}]}}"); }); } private static void withContext(Class configuration, - ConsumerWithException consumer) throws E { + ConsumerWithException consumer) throws E { + + try (AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext()) { + + context.register(configuration); + context.setServletContext(new MockServletContext()); + context.refresh(); - try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configuration)) { consumer.accept(context); } } @@ -387,7 +436,6 @@ public class EnableHypermediaSupportIntegrationTest { assertEntityLinksSetUp(context); assertThat(context.getBean(LinkDiscoverer.class)).isInstanceOf(HalLinkDiscoverer.class); - assertThat(context.getBean(ObjectMapper.class)).isNotNull(); RequestMappingHandlerAdapter rmha = context.getBean(RequestMappingHandlerAdapter.class); assertThat(rmha.getMessageConverters()) @@ -401,7 +449,6 @@ public class EnableHypermediaSupportIntegrationTest { assertEntityLinksSetUp(context); assertThat(context.getBean(LinkDiscoverer.class)).isInstanceOf(HalFormsLinkDiscoverer.class); - assertThat(context.getBean(ObjectMapper.class)).isNotNull(); RequestMappingHandlerAdapter rmha = context.getBean(RequestMappingHandlerAdapter.class); assertThat(rmha.getMessageConverters().get(0)).isInstanceOf(MappingJackson2HttpMessageConverter.class); @@ -409,18 +456,15 @@ public class EnableHypermediaSupportIntegrationTest { }); } - @SuppressWarnings({ "unchecked" }) private static void assertCollectionJsonSetupForConfigClass(Class configClass) { withContext(configClass, context -> { assertEntityLinksSetUp(context); assertThat(context.getBean(LinkDiscoverer.class)).isInstanceOf(CollectionJsonLinkDiscoverer.class); - assertThat(context.getBean(ObjectMapper.class)).isNotNull(); RequestMappingHandlerAdapter rmha = context.getBean(RequestMappingHandlerAdapter.class); assertThat(rmha.getMessageConverters().get(0)).isInstanceOf(MappingJackson2HttpMessageConverter.class); - }); } @@ -448,19 +492,10 @@ public class EnableHypermediaSupportIntegrationTest { } @Configuration + @EnableWebMvc @Import(DelegateConfig.class) static class HalConfig { - static int numberOfMessageConverters = 0; - static int numberOfMessageConvertersLegacy = 0; - - @Bean - public RequestMappingHandlerAdapter rmh() { - RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); - numberOfMessageConverters = adapter.getMessageConverters().size(); - return adapter; - } - @Bean public RestTemplate restTemplate() { return new RestTemplate(); @@ -483,6 +518,7 @@ public class EnableHypermediaSupportIntegrationTest { } + @EnableWebMvc @Configuration @EnableHypermediaSupport(type = HypermediaType.HAL) static class RenderLinkAsSingleLinksConfig { @@ -491,25 +527,11 @@ public class EnableHypermediaSupportIntegrationTest { HalConfiguration halConfiguration() { return new HalConfiguration().withRenderSingleLinks(AS_ARRAY); } - - @Bean - public RequestMappingHandlerAdapter rmh() { - return new RequestMappingHandlerAdapter(); - } } @Import(DelegateHalFormsHypermediaConfig.class) static class HalFormsConfig { - static int numberOfMessageConverters = 0; - - @Bean - public RequestMappingHandlerAdapter rmh() { - RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); - numberOfMessageConverters = adapter.getMessageConverters().size(); - return adapter; - } - @Bean public RestTemplate restTemplate() { return new RestTemplate(); @@ -527,6 +549,7 @@ public class EnableHypermediaSupportIntegrationTest { } + @EnableWebMvc @Configuration @EnableHypermediaSupport(type = HypermediaType.HAL_FORMS) static class DelegateHalFormsHypermediaConfig { @@ -538,20 +561,11 @@ public class EnableHypermediaSupportIntegrationTest { void accept(T element) throws E; } + @EnableWebMvc @Configuration @Import(AlternateDelegateConfig.class) static class CollectionJsonConfig { - static int numberOfMessageConverters = 0; - static int numberOfMessageConvertersLegacy = 0; - - @Bean - public RequestMappingHandlerAdapter rmh() { - RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); - numberOfMessageConverters = adapter.getMessageConverters().size(); - return adapter; - } - @Bean public RestTemplate restTemplate() { return new RestTemplate(); diff --git a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java index 8c4c911e..0648af1d 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsMessageConverterUnitTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-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. @@ -30,6 +30,8 @@ import org.junit.Test; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpInputMessage; import org.springframework.http.HttpMethod; @@ -40,6 +42,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** * @author Greg Turnquist + * @author Oliver Gierke */ public class HalFormsMessageConverterUnitTest { @@ -50,15 +53,13 @@ public class HalFormsMessageConverterUnitTest { public void setUp() { this.mapper = new ObjectMapper(); - this.messageConverter = new HalFormsMessageConverter(this.mapper); - } + this.mapper.registerModule(new Jackson2HalFormsModule()); - @Test - public void verifyBasicAttributes() { + TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( + ResourceSupport.class); + converter.setObjectMapper(mapper); - assertThat(this.messageConverter.getSupportedMediaTypes(), hasItems(MediaTypes.HAL_FORMS_JSON)); - assertThat(this.messageConverter.canRead(HalFormsDocument.class, MediaTypes.HAL_FORMS_JSON), is(true)); - assertThat(this.messageConverter.canWrite(HalFormsDocument.class, MediaTypes.HAL_FORMS_JSON), is(true)); + this.messageConverter = converter; } @Test diff --git a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java index 4138d296..e3fbc149 100644 --- a/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/hal/forms/HalFormsValidationIntegrationTest.java @@ -57,8 +57,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import com.fasterxml.jackson.databind.ObjectMapper; - /** * Test that when an {@link org.springframework.hateoas.Affordance} is included that does NOT match the self link, an * exception is thrown. @@ -72,8 +70,6 @@ public class HalFormsValidationIntegrationTest { @Autowired WebApplicationContext context; - @Autowired ObjectMapper mapper; - MockMvc mockMvc; @Before @@ -148,7 +144,7 @@ public class HalFormsValidationIntegrationTest { // Return the affordance + a link back to the entire collection resource. return new Resource<>(EMPLOYEES.get(id), findOneLink.andAffordances(employeesLink.getAffordances()), - employeesLink); + employeesLink); } @PostMapping("/employees") @@ -159,8 +155,10 @@ public class HalFormsValidationIntegrationTest { EMPLOYEES.put(newEmployeeId, employee); try { - return ResponseEntity.noContent().location(new URI(findOne(newEmployeeId).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) - .build(); + return ResponseEntity.noContent() + .location( + new URI(findOne(newEmployeeId).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) + .build(); } catch (URISyntaxException e) { return ResponseEntity.badRequest().body(e.getMessage()); } @@ -172,8 +170,9 @@ public class HalFormsValidationIntegrationTest { EMPLOYEES.put(id, employee); try { - return ResponseEntity.noContent().location(new URI(findOne(id).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) - .build(); + return ResponseEntity.noContent() + .location(new URI(findOne(id).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) + .build(); } catch (URISyntaxException e) { return ResponseEntity.badRequest().body(e.getMessage()); } @@ -197,8 +196,9 @@ public class HalFormsValidationIntegrationTest { EMPLOYEES.put(id, newEmployee); try { - return ResponseEntity.noContent().location(new URI(findOne(id).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) - .build(); + return ResponseEntity.noContent() + .location(new URI(findOne(id).getLink(Link.REL_SELF).map(link -> link.expand().getHref()).orElse(""))) + .build(); } catch (URISyntaxException e) { return ResponseEntity.badRequest().body(e.getMessage()); }