diff --git a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java index 1d4ff049..6bcc960b 100644 --- a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java +++ b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java @@ -44,6 +44,7 @@ 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.config.EnableHypermediaSupport.HypermediaType; import org.springframework.hateoas.core.AnnotationRelProvider; import org.springframework.hateoas.core.DefaultRelProvider; @@ -52,6 +53,7 @@ import org.springframework.hateoas.core.EvoInflectorRelProvider; import org.springframework.hateoas.hal.CurieProvider; import org.springframework.hateoas.hal.HalLinkDiscoverer; import org.springframework.hateoas.hal.Jackson2HalModule; +import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.plugin.core.PluginRegistry; @@ -278,7 +280,8 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe halObjectMapper.registerModule(new Jackson2HalModule()); halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider)); - MappingJackson2HttpMessageConverter halConverter = new MappingJackson2HttpMessageConverter(); + MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter( + ResourceSupport.class); halConverter.setSupportedMediaTypes(Arrays.asList(HAL_JSON)); halConverter.setObjectMapper(halObjectMapper); diff --git a/src/main/java/org/springframework/hateoas/mvc/TypeConstrainedMappingJackson2HttpMessageConverter.java b/src/main/java/org/springframework/hateoas/mvc/TypeConstrainedMappingJackson2HttpMessageConverter.java new file mode 100644 index 00000000..6c837376 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/mvc/TypeConstrainedMappingJackson2HttpMessageConverter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.mvc; + +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.util.Assert; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * Extension of {@link MappingJackson2HttpMessageConverter} to constrain the ability to read and write HTTP message + * based on the target type. Useful in case the {@link ObjectMapper} about to be configured has customizations that + * sholny be applied to object trees of a certain base type. + * + * @author Oliver Gierke + */ +public class TypeConstrainedMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter { + + private final Class type; + + /** + * Creates a new {@link TypeConstrainedMappingJackson2HttpMessageConverter} for the given type. + * + * @param type must not be {@literal null}. + */ + public TypeConstrainedMappingJackson2HttpMessageConverter(Class type) { + + Assert.notNull(type, "Type must not be null!"); + this.type = type; + } + + /* + * (non-Javadoc) + * @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#canRead(java.lang.Class, org.springframework.http.MediaType) + */ + @Override + public boolean canRead(Class clazz, MediaType mediaType) { + return type.isAssignableFrom(clazz) && super.canRead(clazz, mediaType); + } + + /* + * (non-Javadoc) + * @see org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#canWrite(java.lang.Class, org.springframework.http.MediaType) + */ + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return type.isAssignableFrom(clazz) && super.canWrite(clazz, mediaType); + } +} diff --git a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java index c8f05fdb..2d9cda98 100644 --- a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 the original author or authors. + * Copyright 2013-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import org.springframework.hateoas.config.HypermediaSupportBeanDefinitionRegistr import org.springframework.hateoas.core.DelegatingEntityLinks; import org.springframework.hateoas.core.DelegatingRelProvider; import org.springframework.hateoas.hal.HalLinkDiscoverer; +import org.springframework.hateoas.mvc.TypeConstrainedMappingJackson2HttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.test.util.ReflectionTestUtils; @@ -77,10 +78,46 @@ public class EnableHypermediaSupportIntegrationTest { } @Test - public void foo() { + public void bootstrapsHalConfigurationForSubclass() { assertHalSetupForConfigClass(ExtendedHalConfig.class); } + /** + * @see #134, #219 + */ + @Test + @SuppressWarnings("unchecked") + public void halSetupIsAppliedToAllTransitiveComponentsInRequestMappingHandlerAdapter() { + + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(HalConfig.class); + + Jackson2ModuleRegisteringBeanPostProcessor postProcessor = new HypermediaSupportBeanDefinitionRegistrar.Jackson2ModuleRegisteringBeanPostProcessor(); + postProcessor.setBeanFactory(context); + + RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); + + assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON)); + + boolean found = false; + + for (HandlerMethodArgumentResolver resolver : adapter.getArgumentResolvers().getResolvers()) { + + if (resolver instanceof AbstractMessageConverterMethodArgumentResolver) { + + found = true; + + AbstractMessageConverterMethodArgumentResolver processor = (AbstractMessageConverterMethodArgumentResolver) resolver; + List> converters = (List>) ReflectionTestUtils.getField( + processor, "messageConverters"); + + assertThat(converters.get(0), is(instanceOf(TypeConstrainedMappingJackson2HttpMessageConverter.class))); + assertThat(converters.get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON)); + } + } + + assertThat(found, is(true)); + } + private static void assertEntityLinksSetUp(ApplicationContext context) { Map discoverers = context.getBeansOfType(EntityLinks.class); @@ -110,41 +147,6 @@ public class EnableHypermediaSupportIntegrationTest { Matchers.> hasItems(instanceOf(MappingJackson2HttpMessageConverter.class))); } - /** - * @see #134 - */ - @Test - @SuppressWarnings("unchecked") - public void halSetupIsAppliedToAllTransitiveComponentsInRequestMappingHandlerAdapter() { - - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(HalConfig.class); - - Jackson2ModuleRegisteringBeanPostProcessor postProcessor = new HypermediaSupportBeanDefinitionRegistrar.Jackson2ModuleRegisteringBeanPostProcessor(); - postProcessor.setBeanFactory(context); - - RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); - - assertThat(adapter.getMessageConverters().get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON)); - - boolean found = false; - - for (HandlerMethodArgumentResolver resolver : adapter.getArgumentResolvers().getResolvers()) { - - if (resolver instanceof AbstractMessageConverterMethodArgumentResolver) { - - found = true; - - AbstractMessageConverterMethodArgumentResolver processor = (AbstractMessageConverterMethodArgumentResolver) resolver; - List> converters = (List>) ReflectionTestUtils.getField( - processor, "messageConverters"); - - assertThat(converters.get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON)); - } - } - - assertThat(found, is(true)); - } - @Configuration @Import(DelegateConfig.class) static class HalConfig { diff --git a/src/test/java/org/springframework/hateoas/mvc/TypeConstrainedMappingJackson2HttpMessageConverterUnitTest.java b/src/test/java/org/springframework/hateoas/mvc/TypeConstrainedMappingJackson2HttpMessageConverterUnitTest.java new file mode 100644 index 00000000..636fe979 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/mvc/TypeConstrainedMappingJackson2HttpMessageConverterUnitTest.java @@ -0,0 +1,69 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas.mvc; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.springframework.hateoas.Resource; +import org.springframework.hateoas.ResourceSupport; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; + +/** + * Unit tests for {@link TypeConstrainedMappingJackson2HttpMessageConverter}. + * + * @author Oliver Gierke + */ +public class TypeConstrainedMappingJackson2HttpMessageConverterUnitTest { + + /** + * @see #219 + */ + @Test(expected = IllegalArgumentException.class) + public void rejectsNullType() { + new TypeConstrainedMappingJackson2HttpMessageConverter(null); + } + + /** + * @see #219 + */ + @Test + public void canReadTypeIfAssignableToConfiguredType() { + + HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( + ResourceSupport.class); + + assertThat(converter.canRead(Object.class, MediaType.APPLICATION_JSON), is(false)); + assertThat(converter.canRead(ResourceSupport.class, MediaType.APPLICATION_JSON), is(true)); + assertThat(converter.canRead(Resource.class, MediaType.APPLICATION_JSON), is(true)); + } + + /** + * @see #219 + */ + @Test + public void canWriteTypeIfAssignableToConfiguredType() { + + HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( + ResourceSupport.class); + + assertThat(converter.canWrite(Object.class, MediaType.APPLICATION_JSON), is(false)); + assertThat(converter.canWrite(ResourceSupport.class, MediaType.APPLICATION_JSON), is(true)); + assertThat(converter.canWrite(Resource.class, MediaType.APPLICATION_JSON), is(true)); + } +}