diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/EntityLookupConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/EntityLookupConfiguration.java index 8481cb6c8..63e21d85e 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/EntityLookupConfiguration.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/EntityLookupConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.data.rest.core.config; +import lombok.Getter; import lombok.NonNull; import lombok.RequiredArgsConstructor; import lombok.Value; @@ -30,6 +31,7 @@ import org.springframework.data.repository.core.support.AbstractRepositoryMetada import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.config.EntityLookupRegistrar.LookupRegistrar.Lookup; import org.springframework.data.rest.core.support.EntityLookup; +import org.springframework.data.util.MethodInvocationRecorder; import org.springframework.data.util.StreamUtils; import org.springframework.util.Assert; @@ -169,6 +171,7 @@ class EntityLookupConfiguration implements EntityLookupRegistrar { private final LookupInformation> lookupInfo; private final Repository repository; private final Class domainType; + private final @Getter Optional lookupProperty; /** * Creates a new {@link RepositoriesEntityLookup} for the given {@link Repositories} and {@link LookupInformation}. @@ -192,6 +195,11 @@ class EntityLookupConfiguration implements EntityLookupRegistrar { this.repository = (Repository) repositories.getRepositoryFor(information.getDomainType())// .orElseThrow(() -> new IllegalStateException( "No repository found for type " + information.getDomainType().getName() + "!")); + + this.lookupProperty = Optional.of(domainType) // + .flatMap(it -> MethodInvocationRecorder.forProxyOf(it) // + .record(lookupInfo.identifierMapping::convert) // + .getPropertyPath()); } /* diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ExposureConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ExposureConfiguration.java index f49a76dcb..da7fe1f77 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ExposureConfiguration.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ExposureConfiguration.java @@ -125,7 +125,8 @@ public class ExposureConfiguration implements ExposureConfigurer { } /** - * Returns whether PUT is supported for the given {@link ResourceMetadata}. + * Returns whether PUT requests can be used to create new instances for the type backing the given + * {@link ResourceMetadata}. * * @param metadata must not be {@literal null}. * @return @@ -134,7 +135,20 @@ public class ExposureConfiguration implements ExposureConfigurer { Assert.notNull(metadata, "ResourceMetadata must not be null!"); - return creationViaPut.apply(metadata.getDomainType()); + return allowsPutForCreation(metadata.getDomainType()); + } + + /** + * Returns whether PUT requests can be used to create new instances of the given domain type. + * + * @param metadata must not be {@literal null}. + * @return + */ + public boolean allowsPutForCreation(Class domainType) { + + Assert.notNull(domainType, "Domain type must not be null!"); + + return creationViaPut.apply(domainType); } HttpMethods filter(ConfigurableHttpMethods methods, ResourceType type, ResourceMetadata metadata) { diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/support/EntityLookup.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/support/EntityLookup.java index 7b79b892e..b6e590350 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/support/EntityLookup.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/support/EntityLookup.java @@ -55,4 +55,12 @@ public interface EntityLookup extends Plugin> { * @return can be {@literal null}. */ Optional lookupEntity(Object id); + + /** + * Returns the lookup property if available. If {@link Optional#empty()} is returned, we assume the identifier + * property is the one to be used for lookup. + * + * @return will never be {@literal null}. + */ + Optional getLookupProperty(); } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationIntegrationTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationIntegrationTests.java index 72ff8aed7..4e6dcbbcd 100755 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationIntegrationTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/RepositoryRestConfigurationIntegrationTests.java @@ -19,9 +19,15 @@ import static org.assertj.core.api.Assertions.*; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.core.config.ResourceMapping; import org.springframework.data.rest.core.domain.ConfiguredPersonRepository; +import org.springframework.data.rest.core.domain.Profile; +import org.springframework.data.rest.core.domain.ProfileRepository; +import org.springframework.data.rest.core.support.EntityLookup; +import org.springframework.plugin.core.PluginRegistry; +import org.springframework.test.annotation.DirtiesContext; /** * Tests to check that {@link ResourceMapping}s are handled correctly. @@ -33,6 +39,7 @@ import org.springframework.data.rest.core.domain.ConfiguredPersonRepository; public class RepositoryRestConfigurationIntegrationTests extends AbstractIntegrationTests { @Autowired RepositoryRestConfiguration config; + @Autowired Repositories repositories; @Test public void shouldProvideResourceMappingForConfiguredRepository() throws Exception { @@ -44,4 +51,20 @@ public class RepositoryRestConfigurationIntegrationTests extends AbstractIntegra assertThat(mapping.getPath()).isEqualTo("people"); assertThat(mapping.isExported()).isFalse(); } + + @Test // DATAREST-1304 + @DirtiesContext + public void exposesLookupPropertyFromLambda() { + + config.withEntityLookup() // + .forRepository(ProfileRepository.class) // + .withIdMapping(Profile::getName) // + .withLookup(ProfileRepository::findByName); + + PluginRegistry, Class> lookups = PluginRegistry.of(config.getEntityLookups(repositories)); + + assertThat(lookups.getPluginFor(Profile.class)).hasValueSatisfying(it -> { + assertThat(it.getLookupProperty()).hasValue("name"); + }); + } } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/Profile.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/Profile.java index 5198aa62b..4c39fb050 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/Profile.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/Profile.java @@ -16,6 +16,7 @@ package org.springframework.data.rest.core.domain; import lombok.Value; +import lombok.experimental.NonFinal; import java.util.UUID; @@ -25,6 +26,7 @@ import org.springframework.data.annotation.Id; * @author Jon Brisbin * @author Oliver Gierke */ +@NonFinal @Value public class Profile { diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/ProfileRepository.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/ProfileRepository.java index 7b78a7cf0..8888ba5ed 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/ProfileRepository.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/domain/ProfileRepository.java @@ -25,4 +25,7 @@ import org.springframework.data.repository.CrudRepository; * @author Jon Brisbin * @author Oliver Gierke */ -public interface ProfileRepository extends CrudRepository {} +public interface ProfileRepository extends CrudRepository { + + Profile findByName(String name); +} diff --git a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java index 4c416c34c..5c461a4e5 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java +++ b/spring-data-rest-tests/spring-data-rest-tests-shop/src/main/java/org/springframework/data/rest/tests/shop/Product.java @@ -17,6 +17,7 @@ package org.springframework.data.rest.tests.shop; import lombok.RequiredArgsConstructor; import lombok.Value; +import lombok.experimental.NonFinal; import java.math.BigDecimal; import java.util.UUID; @@ -28,6 +29,7 @@ import org.springframework.data.rest.core.config.Projection; * @author Oliver Gierke * @author Craig Andrews */ +@NonFinal @Value @RequiredArgsConstructor public class Product { diff --git a/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java b/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java index 2d20b4232..78d5cd502 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java +++ b/spring-data-rest-tests/spring-data-rest-tests-shop/src/test/java/org/springframework/data/rest/tests/shop/ShopConfiguration.java @@ -26,9 +26,9 @@ import org.springframework.data.map.repository.config.EnableMapRepositories; import org.springframework.data.rest.core.config.RepositoryRestConfiguration; import org.springframework.data.rest.tests.shop.Customer.Gender; import org.springframework.data.rest.tests.shop.Product.ProductNameOnlyProjection; -import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter; -import org.springframework.hateoas.Link; +import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; import org.springframework.hateoas.EntityModel; +import org.springframework.hateoas.Link; import org.springframework.hateoas.server.RepresentationModelProcessor; /** @@ -96,7 +96,7 @@ public class ShopConfiguration { } @Configuration - static class SpringDataRestConfiguration extends RepositoryRestConfigurerAdapter { + static class SpringDataRestConfiguration implements RepositoryRestConfigurer { /* * (non-Javadoc) diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java index 6f226ff7a..e713fd8f2 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java @@ -15,6 +15,9 @@ */ package org.springframework.data.rest.webmvc.config; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + import java.io.IOException; import java.io.Serializable; import java.util.List; @@ -25,8 +28,12 @@ import javax.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.support.DefaultConversionService; +import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.model.ConvertingPropertyAccessor; +import org.springframework.data.rest.core.support.EntityLookup; import org.springframework.data.rest.webmvc.IncomingRequest; import org.springframework.data.rest.webmvc.PersistentEntityResource; import org.springframework.data.rest.webmvc.PersistentEntityResource.Builder; @@ -40,7 +47,7 @@ import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServletServerHttpRequest; -import org.springframework.util.Assert; +import org.springframework.plugin.core.PluginRegistry; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -56,42 +63,19 @@ import com.fasterxml.jackson.databind.node.ObjectNode; * @author Jon Brisbin * @author Oliver Gierke */ +@RequiredArgsConstructor public class PersistentEntityResourceHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { private static final String ERROR_MESSAGE = "Could not read an object of type %s from the request!"; private static final String NO_CONVERTER_FOUND = "No suitable HttpMessageConverter found to read request body into object of type %s from request with content type of %s!"; - private final RootResourceInformationHandlerMethodArgumentResolver resourceInformationResolver; - private final BackendIdHandlerMethodArgumentResolver idResolver; - private final DomainObjectReader reader; - private final List> messageConverters; + private final @NonNull List> messageConverters; + private final @NonNull RootResourceInformationHandlerMethodArgumentResolver resourceInformationResolver; + private final @NonNull BackendIdHandlerMethodArgumentResolver idResolver; + private final @NonNull DomainObjectReader reader; + private final @NonNull PluginRegistry, Class> lookups; private final ConversionService conversionService = new DefaultConversionService(); - /** - * Creates a new {@link PersistentEntityResourceHandlerMethodArgumentResolver} for the given - * {@link HttpMessageConverter}s and {@link RootResourceInformationHandlerMethodArgumentResolver}.. - * - * @param messageConverters must not be {@literal null}. - * @param resourceInformationResolver must not be {@literal null}. - * @param idResolver must not be {@literal null}. - * @param reader must not be {@literal null}. - */ - public PersistentEntityResourceHandlerMethodArgumentResolver(List> messageConverters, - RootResourceInformationHandlerMethodArgumentResolver resourceInformationResolver, - BackendIdHandlerMethodArgumentResolver idResolver, DomainObjectReader reader) { - - Assert.notEmpty(messageConverters, "MessageConverters must not be null or empty!"); - Assert.notNull(resourceInformationResolver, - "RootResourceInformationHandlerMethodArgumentResolver must not be empty!"); - Assert.notNull(idResolver, "BackendIdHandlerMethodArgumentResolver must not be null!"); - Assert.notNull(reader, "DomainObjectReader must not be null!"); - - this.messageConverters = messageConverters; - this.resourceInformationResolver = resourceInformationResolver; - this.idResolver = idResolver; - this.reader = reader; - } - /* * (non-Javadoc) * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) @@ -129,31 +113,45 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha Optional id = Optional .ofNullable(idResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory)); Optional objectToUpdate = id.flatMap(it -> resourceInformation.getInvoker().invokeFindById(it)); + Object newObject = read(resourceInformation, incoming, converter, objectToUpdate); - Object obj = read(resourceInformation, incoming, converter, objectToUpdate); - - if (obj == null) { - throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, domainType)); + if (newObject == null) { + throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, domainType), request); } PersistentEntity entity = resourceInformation.getPersistentEntity(); + + if (!id.isPresent()) { + return toResource(newObject, entity, false); + } + + PersistentPropertyAccessor accessor = entity.getPropertyAccessor(newObject); + PersistentProperty idProperty = entity.getRequiredIdProperty(); + boolean forUpdate = objectToUpdate.isPresent(); - Optional entityIdentifier = objectToUpdate.map(it -> entity.getIdentifierAccessor(it).getIdentifier()); - entityIdentifier.ifPresent(it -> entity.getPropertyAccessor(obj).setProperty(entity.getRequiredIdProperty(), - entityIdentifier.orElse(null))); + // Transfer identifier from existing object + objectToUpdate.map(entity::getIdentifierAccessor) // + .map(IdentifierAccessor::getIdentifier) // + .ifPresent(it -> accessor.setProperty(idProperty, it)); - id.ifPresent(it -> { - ConvertingPropertyAccessor accessor = new ConvertingPropertyAccessor(entity.getPropertyAccessor(obj), - conversionService); - accessor.setProperty(entity.getRequiredIdProperty(), it); - }); + if (!forUpdate) { - Builder build = PersistentEntityResource.build(obj, entity); - return forUpdate ? build.build() : build.forCreation(); + // Find property to map URI derived value from + PersistentProperty propertyToSet = lookups.getPluginFor(domainType) // + .flatMap(EntityLookup::getLookupProperty) // + .> map(entity::getPersistentProperty) // + .orElseGet(() -> idProperty); + + // Transfer onto new object + new ConvertingPropertyAccessor(accessor, conversionService) // + .setProperty(propertyToSet, id.get()); + } + + return toResource(accessor.getBean(), entity, forUpdate); } - throw new HttpMessageNotReadableException(String.format(NO_CONVERTER_FOUND, domainType, contentType)); + throw new HttpMessageNotReadableException(String.format(NO_CONVERTER_FOUND, domainType, contentType), request); } /** @@ -205,7 +203,8 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha throw (HttpMessageNotReadableException) o_O; } - throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, existingObject.getClass()), o_O); + throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, existingObject.getClass()), o_O, + request.getServerHttpRequest()); } } @@ -219,7 +218,8 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha return handler.applyPut((ObjectNode) jsonNode, existingObject); } catch (Exception o_O) { - throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, existingObject.getClass()), o_O); + throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, existingObject.getClass()), o_O, + request.getServerHttpRequest()); } } @@ -229,7 +229,14 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha try { return converter.read(information.getDomainType(), request.getServerHttpRequest()); } catch (IOException o_O) { - throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, information.getDomainType()), o_O); + throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, information.getDomainType()), o_O, + request.getServerHttpRequest()); } } + + private PersistentEntityResource toResource(Object bean, PersistentEntity entity, boolean forUpdate) { + + Builder build = PersistentEntityResource.build(bean, entity); + return forUpdate ? build.build() : build.forCreation(); + } } 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 d59304418..6ff2840ba 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 @@ -389,9 +389,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon @Bean public PersistentEntityResourceHandlerMethodArgumentResolver persistentEntityArgumentResolver() { + PluginRegistry, Class> lookups = PluginRegistry.of(getEntityLookups()); + return new PersistentEntityResourceHandlerMethodArgumentResolver(defaultMessageConverters(), repoRequestArgumentResolver(), backendIdHandlerMethodArgumentResolver(), - new DomainObjectReader(persistentEntities(), associationLinks())); + new DomainObjectReader(persistentEntities(), associationLinks()), lookups); } /** diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java index 730904137..876532124 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/util/UriUtils.java @@ -17,7 +17,6 @@ package org.springframework.data.rest.webmvc.util; import java.lang.reflect.Method; import java.util.List; -import java.util.Map; import org.springframework.hateoas.server.core.AnnotationMappingDiscoverer; import org.springframework.util.Assert; @@ -51,14 +50,9 @@ public abstract class UriUtils { String mapping = DISCOVERER.getMapping(method); - Map variables = new org.springframework.web.util.UriTemplate(mapping).match(lookupPath); - String value = variables.get(variable); - - if (value != null) { - return value; - } - - return null; + return new org.springframework.web.util.UriTemplate(mapping) // + .match(lookupPath) // + .get(variable); } /** diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolverUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolverUnitTests.java index 784a87a51..10ee5b65e 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolverUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolverUnitTests.java @@ -20,6 +20,7 @@ import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.Arrays; +import java.util.Optional; import javax.servlet.http.HttpServletRequest; @@ -31,6 +32,7 @@ import org.springframework.data.annotation.Id; import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity; import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; import org.springframework.data.repository.support.RepositoryInvoker; +import org.springframework.data.rest.core.support.EntityLookup; import org.springframework.data.rest.webmvc.PersistentEntityResource; import org.springframework.data.rest.webmvc.RootResourceInformation; import org.springframework.data.rest.webmvc.json.DomainObjectReader; @@ -39,6 +41,7 @@ import org.springframework.http.HttpInputMessage; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.plugin.core.PluginRegistry; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.ServletWebRequest; @@ -75,7 +78,8 @@ public class PersistentEntityResourceHandlerMethodArgumentResolverUnitTests { public void returnsAggregateInstanceWithIdentifierPopulatedForPutRequests() throws Exception { PersistentEntityResourceHandlerMethodArgumentResolver argumentResolver = new PersistentEntityResourceHandlerMethodArgumentResolver( - Arrays.> asList(converter), rootResourceResolver, backendIdResolver, reader); + Arrays.> asList(converter), rootResourceResolver, backendIdResolver, reader, + PluginRegistry.empty()); HttpServletRequest request = new MockHttpServletRequest("PUT", "/foo/4711"); @@ -89,6 +93,32 @@ public class PersistentEntityResourceHandlerMethodArgumentResolverUnitTests { }); } + @Test // DATAREST-1304 + @SuppressWarnings("unchecked") + public void setsLookupPropertyForEntitiesWithCustomLookup() throws Exception { + + EntityLookup lookup = mock(EntityLookup.class); + doReturn(Optional.of("name")).when(lookup).getLookupProperty(); + doReturn(true).when(lookup).supports(Foo.class); + + PersistentEntityResourceHandlerMethodArgumentResolver argumentResolver = new PersistentEntityResourceHandlerMethodArgumentResolver( + Arrays.> asList(converter), rootResourceResolver, backendIdResolver, reader, + PluginRegistry.of(Arrays.asList(lookup))); + + HttpServletRequest request = new MockHttpServletRequest("PUT", "/foo/someName"); + + doReturn(new Foo()).when(converter).read(Mockito.any(Class.class), Mockito.any(HttpInputMessage.class)); + mockInvocationOfResolver(backendIdResolver, "someName"); + + Object result = argumentResolver.resolveArgument(null, null, new ServletWebRequest(request), null); + + assertThat(result).isInstanceOfSatisfying(PersistentEntityResource.class, it -> { + assertThat(it.getContent()).isInstanceOfSatisfying(Foo.class, foo -> { + assertThat(foo.name).isEqualTo("someName"); + }); + }); + } + private void setupRootResourceInfoFor(Class type) throws Exception { RootResourceInformation information = mock(RootResourceInformation.class); @@ -113,5 +143,6 @@ public class PersistentEntityResourceHandlerMethodArgumentResolverUnitTests { static class Foo { @Id Long id; + String name; } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java index 1d6b378ed..90fb290ef 100755 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityJackson2ModuleUnitTests.java @@ -49,9 +49,9 @@ import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module. import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer; import org.springframework.data.rest.webmvc.mapping.Associations; import org.springframework.data.rest.webmvc.support.ExcerptProjector; +import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.server.EntityLinks; import org.springframework.hateoas.server.RepresentationModelProcessor; -import org.springframework.hateoas.UriTemplate; import org.springframework.hateoas.server.mvc.RepresentationModelProcessorInvoker; import org.springframework.plugin.core.PluginRegistry; @@ -92,7 +92,8 @@ public class PersistentEntityJackson2ModuleUnitTests { this.persistentEntities = new PersistentEntities(Arrays.asList(mappingContext)); - RepresentationModelProcessorInvoker invoker = new RepresentationModelProcessorInvoker(Collections.> emptyList()); + RepresentationModelProcessorInvoker invoker = new RepresentationModelProcessorInvoker( + Collections.> emptyList()); NestedEntitySerializer nestedEntitySerializer = new NestedEntitySerializer(persistentEntities, new EmbeddedResourcesAssembler(persistentEntities, associations, mock(ExcerptProjector.class)), invoker); @@ -201,6 +202,11 @@ public class PersistentEntityJackson2ModuleUnitTests { public Optional lookupEntity(Object id) { return Optional.of(new Home()); } + + @Override + public Optional getLookupProperty() { + return Optional.empty(); + } } @Getter