diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java index b56a088d..94cf923c 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalConfiguration.java @@ -54,6 +54,12 @@ public class HalConfiguration { */ private final @Wither @Getter boolean applyPropertyNamingStrategy; + /** + * Configures whether to always use collections for embeddeds, even if there's only one entry for a link relation. + * Defaults to {@literal true}. + */ + private final @Wither @Getter boolean enforceEmbeddedCollections; + /** * Creates a new default {@link HalConfiguration} rendering single links as immediate sub-document. */ @@ -62,6 +68,7 @@ public class HalConfiguration { this.renderSingleLinks = RenderSingleLinks.AS_SINGLE; this.singleLinksPerPattern = new LinkedHashMap<>(); this.applyPropertyNamingStrategy = true; + this.enforceEmbeddedCollections = true; } /** diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java index 7accce1f..0df2affd 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/HalMediaTypeConfiguration.java @@ -15,10 +15,13 @@ */ package org.springframework.hateoas.mediatype.hal; +import lombok.RequiredArgsConstructor; + import java.util.List; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.client.LinkDiscoverer; @@ -38,27 +41,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; * @author Oliver Drotbohm */ @Configuration +@RequiredArgsConstructor public class HalMediaTypeConfiguration implements HypermediaMappingInformation { private final LinkRelationProvider relProvider; private final ObjectProvider curieProvider; private final ObjectProvider halConfiguration; private final @Qualifier("messageResolver") MessageResolver resolver; - - /** - * @param relProvider - * @param curieProvider - * @param halConfiguration - * @param resolver - */ - public HalMediaTypeConfiguration(LinkRelationProvider relProvider, ObjectProvider curieProvider, - ObjectProvider halConfiguration, MessageResolver resolver) { - - this.relProvider = relProvider; - this.curieProvider = curieProvider; - this.halConfiguration = halConfiguration; - this.resolver = resolver; - } + private final AutowireCapableBeanFactory beanFactory; @Bean LinkDiscoverer halLinkDisocoverer() { @@ -85,7 +75,7 @@ public class HalMediaTypeConfiguration implements HypermediaMappingInformation { mapper.registerModule(new Jackson2HalModule()); mapper.setHandlerInstantiator(new Jackson2HalModule.HalHandlerInstantiator(relProvider, curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, - halConfiguration.getIfAvailable(HalConfiguration::new))); + halConfiguration.getIfAvailable(HalConfiguration::new), beanFactory)); return mapper; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java index 4ebcd490..b914bfa1 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/Jackson2HalModule.java @@ -52,7 +52,6 @@ import com.fasterxml.jackson.annotation.JsonUnwrapped; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonStreamContext; import com.fasterxml.jackson.core.JsonToken; import com.fasterxml.jackson.core.Version; import com.fasterxml.jackson.databind.*; @@ -809,33 +808,17 @@ public class Jackson2HalModule extends SimpleModule { */ public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver, HalConfiguration halConfiguration) { - this(provider, curieProvider, resolver, true, halConfiguration); + this(provider, curieProvider, resolver, halConfiguration, null); } - /** - * Creates a new {@link HalHandlerInstantiator} using the given {@link LinkRelationProvider}, {@link CurieProvider} - * and {@link MessageResolver} 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 resolver must not be {@literal null}.. - * @param enforceEmbeddedCollections - */ public HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver, - boolean enforceEmbeddedCollections, HalConfiguration halConfiguration) { - this(provider, curieProvider, resolver, enforceEmbeddedCollections, null, halConfiguration); - } - - private HalHandlerInstantiator(LinkRelationProvider provider, CurieProvider curieProvider, MessageResolver resolver, - boolean enforceEmbeddedCollections, @Nullable AutowireCapableBeanFactory delegate, - HalConfiguration halConfiguration) { + HalConfiguration halConfiguration, @Nullable AutowireCapableBeanFactory delegate) { Assert.notNull(provider, "RelProvider must not be null!"); Assert.notNull(curieProvider, "CurieProvider must not be null!"); - EmbeddedMapper mapper = new EmbeddedMapper(provider, curieProvider, enforceEmbeddedCollections); + EmbeddedMapper mapper = new EmbeddedMapper(provider, curieProvider, + halConfiguration.isEnforceEmbeddedCollections()); this.delegate = delegate; diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java index 7aa41bc6..a5802a0d 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMediaTypeConfiguration.java @@ -20,6 +20,7 @@ import lombok.RequiredArgsConstructor; import java.util.List; import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.hateoas.client.LinkDiscoverer; @@ -49,6 +50,7 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation { private final ObjectProvider halFormsConfiguration; private final ObjectProvider halConfiguration; private final MessageResolver resolver; + private final AbstractAutowireCapableBeanFactory beanFactory; @Bean LinkDiscoverer halFormsLinkDiscoverer() { @@ -77,7 +79,7 @@ class HalFormsMediaTypeConfiguration implements HypermediaMappingInformation { mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new Jackson2HalFormsModule.HalFormsHandlerInstantiator(relProvider, - curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, true, configuration)); + curieProvider.getIfAvailable(() -> CurieProvider.NONE), resolver, configuration, beanFactory)); return mapper; } diff --git a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java index 848c93e9..c860e403 100644 --- a/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java +++ b/src/main/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsModule.java @@ -164,13 +164,14 @@ public class Jackson2HalFormsModule extends SimpleModule { private final Map, Object> serializers = new HashMap<>(); public HalFormsHandlerInstantiator(LinkRelationProvider resolver, CurieProvider curieProvider, - MessageResolver accessor, boolean enforceEmbeddedCollections, HalFormsConfiguration configuration) { + MessageResolver accessor, HalFormsConfiguration configuration, AutowireCapableBeanFactory beanFactory) { - super(resolver, curieProvider, accessor, enforceEmbeddedCollections, configuration.getHalConfiguration()); + super(resolver, curieProvider, accessor, configuration.getHalConfiguration(), beanFactory); - EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider, enforceEmbeddedCollections); - HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(configuration, accessor); HalConfiguration halConfiguration = configuration.getHalConfiguration(); + EmbeddedMapper mapper = new EmbeddedMapper(resolver, curieProvider, + halConfiguration.isEnforceEmbeddedCollections()); + HalFormsTemplateBuilder builder = new HalFormsTemplateBuilder(configuration, accessor); this.serializers.put(HalFormsRepresentationModelSerializer.class, new HalFormsRepresentationModelSerializer(builder)); @@ -182,10 +183,9 @@ public class Jackson2HalFormsModule extends SimpleModule { } public HalFormsHandlerInstantiator(LinkRelationProvider relProvider, CurieProvider curieProvider, - MessageResolver resolver, boolean enforceEmbeddedCollections, AutowireCapableBeanFactory beanFactory) { + MessageResolver resolver, AutowireCapableBeanFactory beanFactory) { - this(relProvider, curieProvider, resolver, enforceEmbeddedCollections, - beanFactory.getBean(HalFormsConfiguration.class)); + this(relProvider, curieProvider, resolver, beanFactory.getBean(HalFormsConfiguration.class), beanFactory); } @Nullable diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java index 5011b8da..e5d10ad1 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/HalFormsMessageConverterUnitTest.java @@ -24,6 +24,7 @@ import java.io.OutputStream; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.core.io.ClassPathResource; import org.springframework.hateoas.Link; import org.springframework.hateoas.MediaTypes; @@ -56,7 +57,7 @@ class HalFormsMessageConverterUnitTest { this.mapper.registerModule(new Jackson2HalFormsModule()); this.mapper.setHandlerInstantiator( new Jackson2HalFormsModule.HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), CurieProvider.NONE, - MessageResolver.DEFAULTS_ONLY, true, new HalFormsConfiguration())); + MessageResolver.DEFAULTS_ONLY, new HalFormsConfiguration(), new DefaultListableBeanFactory())); TypeConstrainedMappingJackson2HttpMessageConverter converter = new TypeConstrainedMappingJackson2HttpMessageConverter( RepresentationModel.class); diff --git a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java index b3be199c..d1a6493c 100644 --- a/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java +++ b/src/test/java/org/springframework/hateoas/mediatype/hal/forms/Jackson2HalFormsIntegrationTest.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.context.support.StaticMessageSource; @@ -90,7 +91,8 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator( // - provider, CurieProvider.NONE, MessageResolver.DEFAULTS_ONLY, true, new HalFormsConfiguration())); + provider, CurieProvider.NONE, MessageResolver.DEFAULTS_ONLY, new HalFormsConfiguration(), + new DefaultListableBeanFactory())); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); } @@ -595,7 +597,7 @@ class Jackson2HalFormsIntegrationTest extends AbstractJackson2MarshallingIntegra mapper.registerModule(new Jackson2HalFormsModule()); mapper.setHandlerInstantiator(new HalFormsHandlerInstantiator(new AnnotationLinkRelationProvider(), provider, - MessageResolver.of(messageSource), true, new HalFormsConfiguration())); + MessageResolver.of(messageSource), new HalFormsConfiguration(), new DefaultListableBeanFactory())); mapper.configure(SerializationFeature.INDENT_OUTPUT, true); mapper.setSerializationInclusion(Include.NON_NULL);