diff --git a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java index 071c769b..913f55c1 100644 --- a/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java +++ b/src/main/java/org/springframework/hateoas/config/HypermediaSupportBeanDefinitionRegistrar.java @@ -29,6 +29,7 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; 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; @@ -60,6 +61,7 @@ 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.annotation.AnnotationMethodHandlerAdapter; @@ -107,9 +109,8 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe if (JSONPATH_PRESENT) { AbstractBeanDefinition linkDiscovererBeanDefinition = getLinkDiscovererBeanDefinition(type); - registerBeanDefinition( - new BeanDefinitionHolder(linkDiscovererBeanDefinition, BeanDefinitionReaderUtils.generateBeanName( - linkDiscovererBeanDefinition, registry)), registry); + registerBeanDefinition(new BeanDefinitionHolder(linkDiscovererBeanDefinition, + BeanDefinitionReaderUtils.generateBeanName(linkDiscovererBeanDefinition, registry)), registry); } } @@ -224,7 +225,7 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe */ static class Jackson2ModuleRegisteringBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware { - private BeanFactory beanFactory; + private AutowireCapableBeanFactory beanFactory; /* * (non-Javadoc) @@ -232,7 +233,11 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe */ @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { - this.beanFactory = beanFactory; + + Assert.isInstanceOf(AutowireCapableBeanFactory.class, beanFactory, + "BeanFactory must be an AutowireCapableBeanFactory!"); + + this.beanFactory = (AutowireCapableBeanFactory) beanFactory; } /* @@ -251,8 +256,8 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe if (bean instanceof AnnotationMethodHandlerAdapter) { AnnotationMethodHandlerAdapter adapter = (AnnotationMethodHandlerAdapter) bean; - List> augmentedConverters = potentiallyRegisterModule(Arrays.asList(adapter - .getMessageConverters())); + List> augmentedConverters = potentiallyRegisterModule( + Arrays.asList(adapter.getMessageConverters())); adapter .setMessageConverters(augmentedConverters.toArray(new HttpMessageConverter[augmentedConverters.size()])); } @@ -294,8 +299,8 @@ class HypermediaSupportBeanDefinitionRegistrar implements ImportBeanDefinitionRe MessageSourceAccessor.class); halObjectMapper.registerModule(new Jackson2HalModule()); - halObjectMapper.setHandlerInstantiator( - new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider, linkRelationMessageSource)); + halObjectMapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider, + linkRelationMessageSource, beanFactory)); MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter( ResourceSupport.class); diff --git a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java index 0f48ee3b..a9af12fc 100644 --- a/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/hal/Jackson2HalModule.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.Map; import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.NoSuchMessageException; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.hateoas.Link; @@ -121,22 +122,21 @@ public class Jackson2HalModule extends SimpleModule { private final BeanProperty property; private final CurieProvider curieProvider; private final EmbeddedMapper mapper; - private final MessageSourceAccessor messageSource; + private final MessageSourceAccessor accessor; - public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, - MessageSourceAccessor messageSource) { - this(null, curieProvider, mapper, messageSource); + public HalLinkListSerializer(CurieProvider curieProvider, EmbeddedMapper mapper, MessageSourceAccessor accessor) { + this(null, curieProvider, mapper, accessor); } public HalLinkListSerializer(BeanProperty property, CurieProvider curieProvider, EmbeddedMapper mapper, - MessageSourceAccessor messageSource) { + MessageSourceAccessor accessor) { super(TypeFactory.defaultInstance().constructType(List.class)); this.property = property; this.curieProvider = curieProvider; this.mapper = mapper; - this.messageSource = messageSource; + this.accessor = accessor; } /* @@ -204,7 +204,7 @@ public class Jackson2HalModule extends SimpleModule { } /** - * Wraps the given link into a HAL specifc extension. + * Wraps the given link into a HAL specific extension. * * @param link must not be {@literal null}. * @return @@ -233,8 +233,7 @@ public class Jackson2HalModule extends SimpleModule { Assert.hasText(localRel, "Local relation must not be null or empty!"); try { - return messageSource == null ? null - : messageSource.getMessage(String.format(RELATION_MESSAGE_TEMPLATE, localRel)); + return accessor == null ? null : accessor.getMessage(String.format(RELATION_MESSAGE_TEMPLATE, localRel)); } catch (NoSuchMessageException o_O) { return null; } @@ -247,7 +246,7 @@ public class Jackson2HalModule extends SimpleModule { @Override public JsonSerializer createContextual(SerializerProvider provider, BeanProperty property) throws JsonMappingException { - return new HalLinkListSerializer(property, curieProvider, mapper, messageSource); + return new HalLinkListSerializer(property, curieProvider, mapper, accessor); } /* @@ -681,30 +680,73 @@ public class Jackson2HalModule extends SimpleModule { } } + /** + * HandlerInstantiator to create HAL-specific serializers, deserializers etc. + * + * @author Oliver Gierke + */ public static class HalHandlerInstantiator extends HandlerInstantiator { - private final Map, Object> instanceMap = new HashMap, Object>(); + private final Map, Object> serializers = new HashMap, Object>(); + private final AutowireCapableBeanFactory delegate; - public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, - MessageSourceAccessor messageSource) { - this(resolver, curieProvider, messageSource, true); + /** + * Creates a new {@link HalHandlerInstantiator} using the given {@link RelProvider}, {@link CurieProvider} and + * {@link MessageSourceAccessor} and {@link AutowireCapableBeanFactory}. Registers a prepared + * {@link HalResourcesSerializer} and {@link HalLinkListSerializer} falling back to instantiation using the given + * {@link AutowireCapableBeanFactory} if provided, or simple default constructor instantiation if not. + * + * @param provider must not be {@literal null}. + * @param curieProvider can be {@literal null}. + * @param accessor can be {@literal null}. + * @param beanFactory can be {@literal null} + */ + public HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, MessageSourceAccessor accessor, + AutowireCapableBeanFactory beanFactory) { + this(provider, curieProvider, accessor, true, beanFactory); } - public HalHandlerInstantiator(RelProvider resolver, CurieProvider curieProvider, - MessageSourceAccessor messageSource, boolean enforceEmbeddedCollections) { - - EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider, enforceEmbeddedCollections); - - Assert.notNull(resolver, "RelProvider must not be null!"); - this.instanceMap.put(HalResourcesSerializer.class, new HalResourcesSerializer(mapper)); - this.instanceMap.put(HalLinkListSerializer.class, - new HalLinkListSerializer(curieProvider, mapper, messageSource)); + /** + * Creates a new {@link HalHandlerInstantiator} using the given {@link RelProvider}, {@link CurieProvider} and + * {@link MessageSourceAccessor}. Registers a prepared {@link HalResourcesSerializer} and + * {@link HalLinkListSerializer} falling back to instantiation expecting a default constructor. + * + * @param provider must not be {@literal null}. + * @param curieProvider can be {@literal null}. + * @param messageSourceAccessor can be {@literal null}. + */ + public HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, + MessageSourceAccessor messageSourceAccessor) { + this(provider, curieProvider, messageSourceAccessor, true); } - private Object findInstance(Class type) { + /** + * Creates a new {@link HalHandlerInstantiator} using the given {@link RelProvider}, {@link CurieProvider} and + * {@link MessageSourceAccessor} and whether to enforce embedded collections. Registers a prepared + * {@link HalResourcesSerializer} and {@link HalLinkListSerializer} falling back to instantiation expecting a + * default constructor. + * + * @param provider must not be {@literal null}. + * @param curieProvider can be {@literal null} + * @param accessor can be {@literal null}. + * @param enforceEmbeddedCollections + */ + public HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, MessageSourceAccessor accessor, + boolean enforceEmbeddedCollections) { + this(provider, curieProvider, accessor, enforceEmbeddedCollections, null); + } - Object result = instanceMap.get(type); - return result != null ? result : BeanUtils.instantiateClass(type); + private HalHandlerInstantiator(RelProvider provider, CurieProvider curieProvider, MessageSourceAccessor accessor, + boolean enforceEmbeddedCollections, AutowireCapableBeanFactory delegate) { + + Assert.notNull(provider, "RelProvider must not be null!"); + + EmbeddedMapper mapper = new EmbeddedMapper(provider, curieProvider, enforceEmbeddedCollections); + + this.delegate = delegate; + + this.serializers.put(HalResourcesSerializer.class, new HalResourcesSerializer(mapper)); + this.serializers.put(HalLinkListSerializer.class, new HalLinkListSerializer(curieProvider, mapper, accessor)); } /* @@ -754,6 +796,13 @@ public class Jackson2HalModule extends SimpleModule { public TypeIdResolver typeIdResolverInstance(MapperConfig config, Annotated annotated, Class resolverClass) { return (TypeIdResolver) findInstance(resolverClass); } + + private Object findInstance(Class type) { + + Object result = serializers.get(type); + + return result != null ? result : delegate != null ? delegate.createBean(type) : BeanUtils.instantiateClass(type); + } } /** diff --git a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java index 22c56704..f5101e36 100644 --- a/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/config/EnableHypermediaSupportIntegrationTest.java @@ -97,7 +97,7 @@ public class EnableHypermediaSupportIntegrationTest { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(HalConfig.class); Jackson2ModuleRegisteringBeanPostProcessor postProcessor = new HypermediaSupportBeanDefinitionRegistrar.Jackson2ModuleRegisteringBeanPostProcessor(); - postProcessor.setBeanFactory(context); + postProcessor.setBeanFactory(context.getAutowireCapableBeanFactory()); RequestMappingHandlerAdapter adapter = context.getBean(RequestMappingHandlerAdapter.class); @@ -112,8 +112,8 @@ public class EnableHypermediaSupportIntegrationTest { found = true; AbstractMessageConverterMethodArgumentResolver processor = (AbstractMessageConverterMethodArgumentResolver) resolver; - List> converters = (List>) ReflectionTestUtils.getField( - processor, "messageConverters"); + List> converters = (List>) ReflectionTestUtils + .getField(processor, "messageConverters"); assertThat(converters.get(0), is(instanceOf(TypeConstrainedMappingJackson2HttpMessageConverter.class))); assertThat(converters.get(0).getSupportedMediaTypes(), hasItem(MediaTypes.HAL_JSON));