diff --git a/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java b/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java index b8eb0b3f5..72ab5a0dd 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java +++ b/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java @@ -43,6 +43,7 @@ import org.springframework.data.rest.webmvc.EmbeddedResourcesAssembler; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer; import org.springframework.data.rest.webmvc.mapping.Associations; +import org.springframework.data.rest.webmvc.mapping.DefaultLinkCollector; import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter; import org.springframework.data.rest.webmvc.support.ExcerptProjector; @@ -120,7 +121,7 @@ public class RepositoryTestsConfig { repositories()); Associations associations = new Associations(mappings, config()); - LinkCollector collector = new LinkCollector(persistentEntities(), selfLinkProvider, associations); + LinkCollector collector = new DefaultLinkCollector(persistentEntities(), selfLinkProvider, associations); return new PersistentEntityJackson2Module(associations, persistentEntities(), uriToEntityConverter, collector, invokerFactory, mock(LookupObjectSerializer.class), diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java index 06f71630f..a339e8f46 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java @@ -43,6 +43,7 @@ import org.springframework.data.rest.webmvc.jpa.Person; import org.springframework.data.rest.webmvc.jpa.PersonRepository; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer; import org.springframework.data.rest.webmvc.mapping.Associations; +import org.springframework.data.rest.webmvc.mapping.DefaultLinkCollector; import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter; import org.springframework.data.rest.webmvc.support.ExcerptProjector; @@ -127,7 +128,7 @@ public class RepositoryTestsConfig { repositories()); Associations associations = new Associations(mappings, config()); - LinkCollector collector = new LinkCollector(persistentEntities(), selfLinkProvider, associations); + LinkCollector collector = new DefaultLinkCollector(persistentEntities(), selfLinkProvider, associations); return new PersistentEntityJackson2Module(associations, persistentEntities(), uriToEntityConverter, collector, invokerFactory, mock(LookupObjectSerializer.class), diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.java index ad1811f78..5a2ad33c8 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurer.java @@ -23,6 +23,7 @@ import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener; +import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.Assert; import org.springframework.web.servlet.config.annotation.CorsRegistry; @@ -144,4 +145,15 @@ public interface RepositoryRestConfigurer { default AuditableBeanWrapperFactory customizeAuditableBeanWrapperFactory(AuditableBeanWrapperFactory factory) { return factory; } + + /** + * Customize the {@link LinkCollector} to be used. + * + * @param collector will never be {@literal null}. + * @return must not be {@literal null}. + * @since 3.5 + */ + default LinkCollector customizeLinkCollector(LinkCollector collector) { + return collector; + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurerDelegate.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurerDelegate.java index 1be406fb0..99a3c7c81 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurerDelegate.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestConfigurerDelegate.java @@ -21,6 +21,7 @@ import org.springframework.core.convert.support.ConfigurableConversionService; import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener; +import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.util.Assert; import org.springframework.web.servlet.config.annotation.CorsRegistry; @@ -136,4 +137,18 @@ class RepositoryRestConfigurerDelegate implements RepositoryRestConfigurer { return factory; } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer#customizeLinkCollector(org.springframework.data.rest.webmvc.mapping.LinkCollector) + */ + @Override + public LinkCollector customizeLinkCollector(LinkCollector collector) { + + for (RepositoryRestConfigurer configurer : delegates) { + collector = configurer.customizeLinkCollector(collector); + } + + return collector; + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index 9caf6fc3a..97d3de9f9 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -74,6 +74,7 @@ import org.springframework.data.rest.webmvc.json.*; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.LookupObjectSerializer; import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter.ValueTypeSchemaPropertyCustomizerFactory; import org.springframework.data.rest.webmvc.mapping.Associations; +import org.springframework.data.rest.webmvc.mapping.DefaultLinkCollector; import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.data.rest.webmvc.spi.BackendIdConverter; import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter; @@ -727,7 +728,9 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon @Bean protected LinkCollector linkCollector(PersistentEntities persistentEntities, SelfLinkProvider selfLinkProvider, Associations associationLinks) { - return new LinkCollector(persistentEntities, selfLinkProvider, associationLinks); + + return configurerDelegate.get() + .customizeLinkCollector(new DefaultLinkCollector(persistentEntities, selfLinkProvider, associationLinks)); } @Bean diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/DefaultLinkCollector.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/DefaultLinkCollector.java new file mode 100644 index 000000000..0f9f7b495 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/DefaultLinkCollector.java @@ -0,0 +1,254 @@ +/* + * Copyright 2021 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 + * + * https://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.data.rest.webmvc.mapping; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.springframework.data.mapping.Association; +import org.springframework.data.mapping.MappingException; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; +import org.springframework.data.mapping.SimpleAssociationHandler; +import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.data.rest.core.Path; +import org.springframework.data.rest.core.mapping.ResourceMapping; +import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.data.rest.core.support.SelfLinkProvider; +import org.springframework.hateoas.IanaLinkRelations; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Links; +import org.springframework.util.Assert; + +/** + * A service to collect all standard links that need to be added to a certain object. + * + * @author Oliver Drotbohm + * @since 3.6 + */ +public class DefaultLinkCollector implements LinkCollector { + + private final PersistentEntities entities; + private final Associations associationLinks; + private final SelfLinkProvider links; + + /** + * Creates a new {@link DefaultLinkCollector} for the given {@link PersistentEntities}, {@link SelfLinkProvider} and + * {@link Associations}. + * + * @param entities must not be {@literal null}. + * @param linkProvider must not be {@literal null}. + * @param associationLinks must not be {@literal null}. + */ + public DefaultLinkCollector(PersistentEntities entities, SelfLinkProvider linkProvider, + Associations associationLinks) { + + Assert.notNull(entities, "PersistentEntities must not be null!"); + Assert.notNull(linkProvider, "SelfLinkProvider must not be null!"); + Assert.notNull(associationLinks, "AssociationLinks must not be null!"); + + this.links = linkProvider; + this.entities = entities; + this.associationLinks = associationLinks; + } + + /** + * Returns all {@link Links} for the given object. + * + * @param object must not be {@literal null}. + * @return + */ + @Override + public Links getLinksFor(Object object) { + return getLinksFor(object, Links.NONE); + } + + /** + * Returns all {@link Links} for the given object and already existing {@link Link}. + * + * @param object must not be {@literal null}. + * @param existingLinks must not be {@literal null}. + * @return + */ + @Override + public Links getLinksFor(Object object, Links existingLinks) { + + Assert.notNull(object, "Object must not be null!"); + Assert.notNull(existingLinks, "Existing links must not be null!"); + + Link selfLink = createSelfLink(object, existingLinks); + + if (selfLink == null) { + return existingLinks; + } + + Path path = new Path(selfLink.expand().getHref()); + + LinkCollectingAssociationHandler handler = new LinkCollectingAssociationHandler(path, associationLinks); + entities.getRequiredPersistentEntity(object.getClass()).doWithAssociations(handler); + + return addSelfLinkIfNecessary(object, existingLinks.and(handler.getLinks())); + } + + @Override + public Links getLinksForNested(Object object, Links existing) { + + PersistentEntity entity = entities.getRequiredPersistentEntity(object.getClass()); + + NestedLinkCollectingAssociationHandler handler = new NestedLinkCollectingAssociationHandler(links, + entity.getPropertyAccessor(object), associationLinks); + entity.doWithAssociations(handler); + + return existing.and(handler.getLinks()); + } + + private Links addSelfLinkIfNecessary(Object object, Links existing) { + return existing.andIf(!existing.hasLink(IanaLinkRelations.SELF), + () -> links.createSelfLinkFor(object).withSelfRel()); + } + + private Link createSelfLink(Object object, Links existing) { + + return existing.getLink(IanaLinkRelations.SELF) // + .orElseGet(() -> links.createSelfLinkFor(object).withSelfRel()); + } + + /** + * {@link SimpleAssociationHandler} that will collect {@link Link}s for all linkable associations. + * + * @author Oliver Gierke + * @since 2.1 + */ + private static class LinkCollectingAssociationHandler implements SimpleAssociationHandler { + + private static final String AMBIGUOUS_ASSOCIATIONS = "Detected multiple association links with same relation type! Disambiguate association %s using @RestResource!"; + + private final Path basePath; + private final Associations associationLinks; + private final List links = new ArrayList(); + + public LinkCollectingAssociationHandler(Path basePath, Associations associationLinks) { + + Assert.notNull(basePath, "Base Path must not be null!"); + Assert.notNull(associationLinks, "Associations must not be null!"); + + this.basePath = basePath; + this.associationLinks = associationLinks; + } + + /** + * Returns the links collected after the {@link Association} has been traversed. + * + * @return the links + */ + public Links getLinks() { + return Links.of(links); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association) + */ + @Override + public void doWithAssociation(final Association> association) { + + if (associationLinks.isLinkableAssociation(association)) { + + PersistentProperty property = association.getInverse(); + Links existingLinks = Links.of(links); + + for (Link link : associationLinks.getLinksFor(association, basePath)) { + if (existingLinks.hasLink(link.getRel())) { + throw new MappingException(String.format(AMBIGUOUS_ASSOCIATIONS, property.toString())); + } else { + links.add(link); + } + } + } + } + } + + private static class NestedLinkCollectingAssociationHandler implements SimpleAssociationHandler { + + private final SelfLinkProvider selfLinks; + private final PersistentPropertyAccessor accessor; + private final Associations associations; + private Links links = Links.NONE; + + public NestedLinkCollectingAssociationHandler(SelfLinkProvider selfLinks, + PersistentPropertyAccessor accessor, Associations associations) { + + Assert.notNull(selfLinks, "SelfLinkProvider must not be null!"); + Assert.notNull(accessor, "PersistentPropertyAccessor must not be null!"); + Assert.notNull(associations, "Associations must not be null!"); + + this.selfLinks = selfLinks; + this.accessor = accessor; + this.associations = associations; + } + + public List getLinks() { + return this.links.toList(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association) + */ + @Override + public void doWithAssociation(Association> association) { + + if (!associations.isLinkableAssociation(association)) { + return; + } + + PersistentProperty property = association.getInverse(); + Object value = accessor.getProperty(property); + + if (value == null) { + return; + } + + ResourceMetadata metadata = associations.getMappings().getMetadataFor(property.getOwner().getType()); + ResourceMapping propertyMapping = metadata.getMappingFor(property); + + for (Object element : asCollection(value)) { + + links = links.andIf(element != null, + () -> selfLinks.createSelfLinkFor(property.getAssociationTargetType(), element) + .withRel(propertyMapping.getRel())); + } + } + + /** + * Returns the given object as {@link Collection}, i.e. the object as is if it's a collection already or wrapped + * into a single-element collection otherwise. + * + * @param object can be {@literal null}. + * @return + */ + @SuppressWarnings("unchecked") + private static Collection asCollection(Object object) { + + return object instanceof Collection // + ? (Collection) object // + : Collections.singleton(object); + } + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/LinkCollector.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/LinkCollector.java index fd1be4cd0..5127a2ee0 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/LinkCollector.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/LinkCollector.java @@ -15,55 +15,15 @@ */ package org.springframework.data.rest.webmvc.mapping; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import org.springframework.data.mapping.Association; -import org.springframework.data.mapping.MappingException; -import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.mapping.PersistentProperty; -import org.springframework.data.mapping.PersistentPropertyAccessor; -import org.springframework.data.mapping.SimpleAssociationHandler; -import org.springframework.data.mapping.context.PersistentEntities; -import org.springframework.data.rest.core.Path; -import org.springframework.data.rest.core.mapping.ResourceMapping; -import org.springframework.data.rest.core.mapping.ResourceMetadata; -import org.springframework.data.rest.core.support.SelfLinkProvider; -import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Link; import org.springframework.hateoas.Links; -import org.springframework.util.Assert; /** * A service to collect all standard links that need to be added to a certain object. * * @author Oliver Gierke */ -public class LinkCollector { - - private final PersistentEntities entities; - private final Associations associationLinks; - private final SelfLinkProvider links; - - /** - * Creates a new {@link PersistentEntities}, {@link SelfLinkProvider} and {@link Associations}. - * - * @param entities must not be {@literal null}. - * @param linkProvider must not be {@literal null}. - * @param associationLinks must not be {@literal null}. - */ - public LinkCollector(PersistentEntities entities, SelfLinkProvider linkProvider, Associations associationLinks) { - - Assert.notNull(entities, "PersistentEntities must not be null!"); - Assert.notNull(linkProvider, "SelfLinkProvider must not be null!"); - Assert.notNull(associationLinks, "AssociationLinks must not be null!"); - - this.links = linkProvider; - this.entities = entities; - this.associationLinks = associationLinks; - } +public interface LinkCollector { /** * Returns all {@link Links} for the given object. @@ -71,189 +31,16 @@ public class LinkCollector { * @param object must not be {@literal null}. * @return */ - public Links getLinksFor(Object object) { - return getLinksFor(object, Links.NONE); - } + Links getLinksFor(Object object); /** * Returns all {@link Links} for the given object and already existing {@link Link}. * * @param object must not be {@literal null}. - * @param existingLinks must not be {@literal null}. + * @param existing must not be {@literal null}. * @return */ - public Links getLinksFor(Object object, Links existingLinks) { + Links getLinksFor(Object object, Links existing); - Assert.notNull(object, "Object must not be null!"); - Assert.notNull(existingLinks, "Existing links must not be null!"); - - Link selfLink = createSelfLink(object, existingLinks); - - if (selfLink == null) { - return existingLinks; - } - - Path path = new Path(selfLink.expand().getHref()); - - LinkCollectingAssociationHandler handler = new LinkCollectingAssociationHandler(path, associationLinks); - entities.getRequiredPersistentEntity(object.getClass()).doWithAssociations(handler); - - return addSelfLinkIfNecessary(object, existingLinks.and(handler.getLinks())); - } - - public Links getLinksForNested(Object object, Links existing) { - - PersistentEntity entity = entities.getRequiredPersistentEntity(object.getClass()); - - NestedLinkCollectingAssociationHandler handler = new NestedLinkCollectingAssociationHandler(links, - entity.getPropertyAccessor(object), associationLinks); - entity.doWithAssociations(handler); - - return existing.and(handler.getLinks()); - } - - private Links addSelfLinkIfNecessary(Object object, Links existing) { - return existing.andIf(!existing.hasLink(IanaLinkRelations.SELF), - () -> links.createSelfLinkFor(object).withSelfRel()); - } - - private Link createSelfLink(Object object, Links existing) { - - return existing.getLink(IanaLinkRelations.SELF) // - .orElseGet(() -> links.createSelfLinkFor(object).withSelfRel()); - } - - /** - * {@link SimpleAssociationHandler} that will collect {@link Link}s for all linkable associations. - * - * @author Oliver Gierke - * @since 2.1 - */ - private static class LinkCollectingAssociationHandler implements SimpleAssociationHandler { - - private static final String AMBIGUOUS_ASSOCIATIONS = "Detected multiple association links with same relation type! Disambiguate association %s using @RestResource!"; - - private final Path basePath; - private final Associations associationLinks; - private final List links = new ArrayList(); - - public LinkCollectingAssociationHandler(Path basePath, Associations associationLinks) { - - Assert.notNull(basePath, "Base Path must not be null!"); - Assert.notNull(associationLinks, "Associations must not be null!"); - - this.basePath = basePath; - this.associationLinks = associationLinks; - } - - /** - * Returns the links collected after the {@link Association} has been traversed. - * - * @return the links - */ - public Links getLinks() { - return Links.of(links); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association) - */ - @Override - public void doWithAssociation(final Association> association) { - - if (associationLinks.isLinkableAssociation(association)) { - - PersistentProperty property = association.getInverse(); - Links existingLinks = Links.of(links); - - for (Link link : associationLinks.getLinksFor(association, basePath)) { - if (existingLinks.hasLink(link.getRel())) { - throw new MappingException(String.format(AMBIGUOUS_ASSOCIATIONS, property.toString())); - } else { - links.add(link); - } - } - } - } - } - - private static class NestedLinkCollectingAssociationHandler implements SimpleAssociationHandler { - - private final SelfLinkProvider selfLinks; - private final PersistentPropertyAccessor accessor; - private final Associations associations; - private Links links = Links.NONE; - - public NestedLinkCollectingAssociationHandler(SelfLinkProvider selfLinks, - PersistentPropertyAccessor accessor, Associations associations) { - - Assert.notNull(selfLinks, "SelfLinkProvider must not be null!"); - Assert.notNull(accessor, "PersistentPropertyAccessor must not be null!"); - Assert.notNull(associations, "Associations must not be null!"); - - this.selfLinks = selfLinks; - this.accessor = accessor; - this.associations = associations; - } - - public List getLinks() { - return this.links.toList(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.mapping.SimpleAssociationHandler#doWithAssociation(org.springframework.data.mapping.Association) - */ - @Override - public void doWithAssociation(Association> association) { - - if (!associations.isLinkableAssociation(association)) { - return; - } - - PersistentProperty property = association.getInverse(); - Object value = accessor.getProperty(property); - - if (value == null) { - return; - } - - ResourceMetadata metadata = associations.getMappings().getMetadataFor(property.getOwner().getType()); - ResourceMapping propertyMapping = metadata.getMappingFor(property); - - for (Object element : asCollection(value)) { - - links = links.andIf(element != null, - () -> selfLinks.createSelfLinkFor(property.getAssociationTargetType(), element) - .withRel(propertyMapping.getRel())); - } - } - - /** - * Returns a link pointing to the given entity using the given {@link ResourceMapping} to detect the link relation. - * - * @param entity must not be {@literal null}. - * @param mapping must not be {@literal null}. - * @return - */ - private Link getLinkFor(Object entity, ResourceMapping mapping) { - return selfLinks.createSelfLinkFor(entity).withRel(mapping.getRel()); - } - - /** - * Returns the given object as {@link Collection}, i.e. the object as is if it's a collection already or wrapped - * into a single-element collection otherwise. - * - * @param object can be {@literal null}. - * @return - */ - @SuppressWarnings("unchecked") - private static Collection asCollection(Object object) { - - return object instanceof Collection // - ? (Collection) object // - : Collections.singleton(object); - } - } + Links getLinksForNested(Object object, Links existing); } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvConfigurationIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvConfigurationIntegrationTests.java index 690a9c043..71c76613a 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvConfigurationIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvConfigurationIntegrationTests.java @@ -47,6 +47,7 @@ import org.springframework.data.rest.webmvc.RepositoryLinksResource; import org.springframework.data.rest.webmvc.RestMediaTypes; import org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter; import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module; +import org.springframework.data.rest.webmvc.mapping.LinkCollector; import org.springframework.data.util.Lazy; import org.springframework.data.util.Streamable; import org.springframework.data.web.HateoasPageableHandlerMethodArgumentResolver; @@ -221,6 +222,14 @@ public class RepositoryRestMvConfigurationIntegrationTests { assertThat(factory).isEqualTo(ExtendingConfiguration.auditableBeanWrapperFactory); } + @Test // #2042 + public void appliesLinkCollectorCustomizer() { + + LinkCollector factory = context.getBean(LinkCollector.class); + + assertThat(factory).isEqualTo(ExtendingConfiguration.collector); + } + private static ObjectMapper getObjectMapper() { AbstractJackson2HttpMessageConverter converter = context.getBean("halJacksonHttpMessageConverter", @@ -233,6 +242,7 @@ public class RepositoryRestMvConfigurationIntegrationTests { static class ExtendingConfiguration { static AuditableBeanWrapperFactory auditableBeanWrapperFactory = mock(AuditableBeanWrapperFactory.class); + static LinkCollector collector = mock(LinkCollector.class); @Bean DefaultLinkRelationProvider relProvider() { @@ -265,8 +275,8 @@ public class RepositoryRestMvConfigurationIntegrationTests { } @Bean - @Order(300) // #2040 - RepositoryRestConfigurer auditingBeanWrapperCustomizer() { + @Order(300) // #2040, #2042 + RepositoryRestConfigurer customizer() { return new RepositoryRestConfigurer() { @@ -274,6 +284,11 @@ public class RepositoryRestMvConfigurationIntegrationTests { public AuditableBeanWrapperFactory customizeAuditableBeanWrapperFactory(AuditableBeanWrapperFactory factory) { return auditableBeanWrapperFactory; } + + @Override + public LinkCollector customizeLinkCollector(LinkCollector collector) { + return ExtendingConfiguration.collector; + } }; } }