From ccdeae7bbd68b23d6cbd07a08bdb44114897bbf5 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Fri, 1 Apr 2016 10:40:49 +0200 Subject: [PATCH] DATAREST-791 - Association resources now considers customized id lookup. RepositoryPropertyReferenceController now uses a RepositoryInvoker instead of the ConversionService so that potentially applied customized entity lookups are considered during that lookup. Extracted HttpHeadersPreparer to remove functionality and dependencies from the commons superclass of all Spring Data REST controllers in favor of a dedicated type. Related tickets: DATAREST-724. --- .../AbstractRepositoryRestController.java | 66 +--------- .../data/rest/webmvc/HttpHeadersPreparer.java | 113 ++++++++++++++++ .../rest/webmvc/RepositoryController.java | 6 +- .../webmvc/RepositoryEntityController.java | 23 ++-- ...RepositoryPropertyReferenceController.java | 46 +++---- .../webmvc/RepositorySearchController.java | 6 +- ...yPropertyReferenceControllerUnitTests.java | 122 ++++++++++++++++++ 7 files changed, 275 insertions(+), 107 deletions(-) create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java index 09ae30e4d..220a52a96 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java @@ -19,22 +19,17 @@ import static org.springframework.data.rest.webmvc.ControllerUtils.*; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.List; -import org.springframework.data.auditing.AuditableBeanWrapper; import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.domain.Page; -import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.rest.core.mapping.ResourceMetadata; -import org.springframework.data.rest.webmvc.support.ETag; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.Link; import org.springframework.hateoas.Resource; import org.springframework.hateoas.ResourceSupport; import org.springframework.hateoas.Resources; import org.springframework.hateoas.core.EmbeddedWrappers; -import org.springframework.http.HttpHeaders; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; @@ -50,23 +45,18 @@ class AbstractRepositoryRestController { private static final EmbeddedWrappers WRAPPERS = new EmbeddedWrappers(false); private final PagedResourcesAssembler pagedResourcesAssembler; - private final AuditableBeanWrapperFactory auditableBeanWrapperFactory; /** * Creates a new {@link AbstractRepositoryRestController} for the given {@link PagedResourcesAssembler} and * {@link AuditableBeanWrapperFactory}. * * @param pagedResourcesAssembler must not be {@literal null}. - * @param auditableBeanWrapperFactory must not be {@literal null}. */ - public AbstractRepositoryRestController(PagedResourcesAssembler pagedResourcesAssembler, - AuditableBeanWrapperFactory auditableBeanWrapperFactory) { + public AbstractRepositoryRestController(PagedResourcesAssembler pagedResourcesAssembler) { Assert.notNull(pagedResourcesAssembler, "PagedResourcesAssembler must not be null!"); - Assert.notNull(auditableBeanWrapperFactory, "AuditableBeanWrapperFactory must not be null!"); this.pagedResourcesAssembler = pagedResourcesAssembler; - this.auditableBeanWrapperFactory = auditableBeanWrapperFactory; } protected Link resourceLink(RootResourceInformation resourceLink, Resource resource) { @@ -124,8 +114,8 @@ class AbstractRepositoryRestController { return pagedResourcesAssembler.toEmptyResource(page, domainType, baseLink); } - return baseLink == null ? pagedResourcesAssembler.toResource(page, assembler) : pagedResourcesAssembler.toResource( - page, assembler, baseLink); + return baseLink == null ? pagedResourcesAssembler.toResource(page, assembler) + : pagedResourcesAssembler.toResource(page, assembler, baseLink); } protected Resources entitiesToResources(Iterable entities, PersistentEntityResourceAssembler assembler, @@ -146,56 +136,6 @@ class AbstractRepositoryRestController { return new Resources>(resources, getDefaultSelfLink()); } - /** - * Returns the default headers to be returned for the given {@link PersistentEntityResource}. Will set {@link ETag} - * and {@code Last-Modified} headers if applicable. - * - * @param resource can be {@literal null}. - * @return - */ - protected HttpHeaders prepareHeaders(PersistentEntityResource resource) { - return resource == null ? new HttpHeaders() : prepareHeaders(resource.getPersistentEntity(), resource.getContent()); - } - - /** - * Retruns the default headers to be returned for the given {@link PersistentEntity} and value. Will set {@link ETag} - * and {@code Last-Modified} headers if applicable. - * - * @param entity must not be {@literal null}. - * @param value must not be {@literal null}. - * @return - */ - protected HttpHeaders prepareHeaders(PersistentEntity entity, Object value) { - - // Add ETag - HttpHeaders headers = ETag.from(entity, value).addTo(new HttpHeaders()); - - // Add Last-Modified - AuditableBeanWrapper wrapper = getAuditableBeanWrapper(value); - - if (wrapper == null) { - return headers; - } - - Calendar lastModifiedDate = wrapper.getLastModifiedDate(); - - if (lastModifiedDate != null) { - headers.setLastModified(lastModifiedDate.getTimeInMillis()); - } - - return headers; - } - - /** - * Returns the {@link AuditableBeanWrapper} for the given source. - * - * @param source can be {@literal null}. - * @return - */ - protected AuditableBeanWrapper getAuditableBeanWrapper(Object source) { - return auditableBeanWrapperFactory.getBeanWrapperFor(source); - } - protected Link getDefaultSelfLink() { return new Link(ServletUriComponentsBuilder.fromCurrentRequest().build().toUriString()); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java new file mode 100644 index 000000000..afbab75c0 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/HttpHeadersPreparer.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 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.data.rest.webmvc; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; + +import java.util.Calendar; + +import org.springframework.data.auditing.AuditableBeanWrapper; +import org.springframework.data.auditing.AuditableBeanWrapperFactory; +import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.rest.webmvc.support.ETag; +import org.springframework.http.HttpHeaders; +import org.springframework.util.Assert; + +/** + * Value object to prepare {@link HttpHeaders} for {@link PersistentEntityResource} and {@link PersistentEntity} + * instances. + * + * @author Oliver Gierke + * @soundtrack Ron Spielman Trio - Matchstick + */ +@RequiredArgsConstructor +public class HttpHeadersPreparer { + + private final @NonNull AuditableBeanWrapperFactory auditableBeanWrapperFactory; + + /** + * Returns the default headers to be returned for the given {@link PersistentEntityResource}. Will set {@link ETag} + * and {@code Last-Modified} headers if applicable. + * + * @param resource can be {@literal null}. + * @return + */ + public HttpHeaders prepareHeaders(PersistentEntityResource resource) { + return resource == null ? new HttpHeaders() : prepareHeaders(resource.getPersistentEntity(), resource.getContent()); + } + + /** + * Returns the default headers to be returned for the given {@link PersistentEntity} and value. Will set {@link ETag} + * and {@code Last-Modified} headers if applicable. + * + * @param entity must not be {@literal null}. + * @param value must not be {@literal null}. + * @return + */ + public HttpHeaders prepareHeaders(PersistentEntity entity, Object value) { + + // Add ETag + HttpHeaders headers = ETag.from(entity, value).addTo(new HttpHeaders()); + + // Add Last-Modified + AuditableBeanWrapper wrapper = getAuditableBeanWrapper(value); + + if (wrapper == null) { + return headers; + } + + Calendar lastModifiedDate = wrapper.getLastModifiedDate(); + + if (lastModifiedDate != null) { + headers.setLastModified(lastModifiedDate.getTimeInMillis()); + } + + return headers; + } + + /** + * Returns whether the given object is still valid in the context of the given {@link HttpHeaders}' requirements. + * + * @param source must not be {@literal null}. + * @param headers must not be {@literal null}. + * @return + */ + public boolean isObjectStillValid(Object source, HttpHeaders headers) { + + Assert.notNull(source, "Source object must not be null!"); + Assert.notNull(headers, "HttpHeaders must not be null!"); + + if (headers.getIfModifiedSince() == -1) { + return false; + } + + AuditableBeanWrapper wrapper = auditableBeanWrapperFactory.getBeanWrapperFor(source); + long current = wrapper.getLastModifiedDate().getTimeInMillis() / 1000 * 1000; + + return current <= headers.getIfModifiedSince(); + } + + /** + * Returns the {@link AuditableBeanWrapper} for the given source. + * + * @param source can be {@literal null}. + * @return + */ + private AuditableBeanWrapper getAuditableBeanWrapper(Object source) { + return auditableBeanWrapperFactory.getBeanWrapperFor(source); + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java index 0518ad610..e0d02283e 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryController.java @@ -18,7 +18,6 @@ package org.springframework.data.rest.webmvc; import java.util.Collections; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.core.mapping.ResourceMappings; import org.springframework.data.rest.core.mapping.ResourceMetadata; @@ -54,13 +53,12 @@ public class RepositoryController extends AbstractRepositoryRestController { * @param repositories must not be {@literal null}. * @param entityLinks must not be {@literal null}. * @param mappings must not be {@literal null}. - * @param auditableBeanWrapperFactory must not be {@literal null}. */ @Autowired public RepositoryController(PagedResourcesAssembler assembler, Repositories repositories, - EntityLinks entityLinks, ResourceMappings mappings, AuditableBeanWrapperFactory auditableBeanWrapperFactory) { + EntityLinks entityLinks, ResourceMappings mappings) { - super(assembler, auditableBeanWrapperFactory); + super(assembler); Assert.notNull(repositories, "Repositories must not be null!"); Assert.notNull(entityLinks, "EntityLinks must not be null!"); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java index 1f1a5d50b..92029c3ac 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.convert.ConversionService; -import org.springframework.data.auditing.AuditableBeanWrapper; import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.domain.Sort; import org.springframework.data.mapping.PersistentEntity; @@ -94,6 +93,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem private final RepositoryEntityLinks entityLinks; private final RepositoryRestConfiguration config; private final ConversionService conversionService; + private final HttpHeadersPreparer headersPreparer; private ApplicationEventPublisher publisher; @@ -115,11 +115,12 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem @Qualifier("defaultConversionService") ConversionService conversionService, AuditableBeanWrapperFactory auditableBeanWrapperFactory) { - super(assembler, auditableBeanWrapperFactory); + super(assembler); this.entityLinks = entityLinks; this.config = config; this.conversionService = conversionService; + this.headersPreparer = new HttpHeadersPreparer(auditableBeanWrapperFactory); } /* @@ -317,7 +318,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem Links links = new Links(assembler.toResource(domainObject).getLinks()); - HttpHeaders headers = prepareHeaders(resourceInformation.getPersistentEntity(), domainObject); + HttpHeaders headers = headersPreparer.prepareHeaders(resourceInformation.getPersistentEntity(), domainObject); headers.add(LINK_HEADER, links.toString()); return new ResponseEntity(headers, HttpStatus.NO_CONTENT); @@ -350,7 +351,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem List ifNoneMatch = headers.getIfNoneMatch(); ETag eTag = ifNoneMatch.isEmpty() ? ETag.NO_ETAG : ETag.from(ifNoneMatch.get(0)); PersistentEntity entity = resourceInformation.getPersistentEntity(); - HttpHeaders responseHeaders = prepareHeaders(entity, domainObj); + HttpHeaders responseHeaders = headersPreparer.prepareHeaders(entity, domainObj); if (eTag.matches(entity, domainObj)) { return new ResponseEntity>(responseHeaders, HttpStatus.NOT_MODIFIED); @@ -358,14 +359,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem // Check last modification for If-Modified-Since - if (headers.getIfModifiedSince() != -1) { - - AuditableBeanWrapper wrapper = getAuditableBeanWrapper(domainObj); - long current = wrapper.getLastModifiedDate().getTimeInMillis() / 1000 * 1000; - - if (current <= headers.getIfModifiedSince()) { - return new ResponseEntity>(responseHeaders, HttpStatus.NOT_MODIFIED); - } + if (headersPreparer.isObjectStillValid(domainObj, headers)) { + return new ResponseEntity>(responseHeaders, HttpStatus.NOT_MODIFIED); } PersistentEntityResource resource = assembler.toFullResource(domainObj); @@ -489,7 +484,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem publisher.publishEvent(new AfterSaveEvent(domainObject)); PersistentEntityResource resource = assembler.toFullResource(obj); - HttpHeaders headers = prepareHeaders(resource); + HttpHeaders headers = headersPreparer.prepareHeaders(resource); if (PUT.equals(httpMethod)) { addLocationHeader(headers, assembler, obj); @@ -518,7 +513,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem PersistentEntityResource resource = returnBody ? assembler.toFullResource(savedObject) : null; - HttpHeaders headers = prepareHeaders(resource); + HttpHeaders headers = headersPreparer.prepareHeaders(resource); addLocationHeader(headers, assembler, savedObject); return ControllerUtils.toResponseEntity(HttpStatus.CREATED, headers, resource); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index f856cba9f..d87ebb173 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -32,18 +32,16 @@ import java.util.Map; import java.util.Map.Entry; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.core.CollectionFactory; -import org.springframework.core.convert.ConversionService; -import org.springframework.data.auditing.AuditableBeanWrapperFactory; 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.repository.support.Repositories; import org.springframework.data.repository.support.RepositoryInvoker; +import org.springframework.data.repository.support.RepositoryInvokerFactory; import org.springframework.data.rest.core.event.AfterLinkDeleteEvent; import org.springframework.data.rest.core.event.AfterLinkSaveEvent; import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent; @@ -76,26 +74,25 @@ import org.springframework.web.bind.annotation.RequestMapping; */ @RepositoryRestController @SuppressWarnings({ "unchecked" }) -class RepositoryPropertyReferenceController extends AbstractRepositoryRestController implements - ApplicationEventPublisherAware { +class RepositoryPropertyReferenceController extends AbstractRepositoryRestController + implements ApplicationEventPublisherAware { private static final String BASE_MAPPING = "/{repository}/{id}/{property}"; private static final Collection AUGMENTING_METHODS = Arrays.asList(HttpMethod.PATCH, HttpMethod.POST); private final Repositories repositories; - private final ConversionService conversionService; + private final RepositoryInvokerFactory repositoryInvokerFactory; private ApplicationEventPublisher publisher; @Autowired public RepositoryPropertyReferenceController(Repositories repositories, - @Qualifier("defaultConversionService") ConversionService conversionService, - PagedResourcesAssembler assembler, AuditableBeanWrapperFactory auditableBeanWrapperFactory) { + RepositoryInvokerFactory repositoryInvokerFactory, PagedResourcesAssembler assembler) { - super(assembler, auditableBeanWrapperFactory); + super(assembler); this.repositories = repositories; - this.conversionService = conversionService; + this.repositoryInvokerFactory = repositoryInvokerFactory; } /* @@ -109,8 +106,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING, method = GET) public ResponseEntity followPropertyReference(final RootResourceInformation repoRequest, - @BackendId Serializable id, final @PathVariable String property, final PersistentEntityResourceAssembler assembler) - throws Exception { + @BackendId Serializable id, final @PathVariable String property, + final PersistentEntityResourceAssembler assembler) throws Exception { final HttpHeaders headers = new HttpHeaders(); @@ -239,7 +236,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro produces = { SPRING_DATA_COMPACT_JSON_VALUE, TEXT_URI_LIST_VALUE }) public ResponseEntity followPropertyReferenceCompact(RootResourceInformation repoRequest, @BackendId Serializable id, @PathVariable String property, PersistentEntityResourceAssembler assembler) - throws Exception { + throws Exception { ResponseEntity response = followPropertyReference(repoRequest, id, property, assembler); @@ -287,9 +284,9 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING, method = { PATCH, PUT, POST }, // consumes = { MediaType.APPLICATION_JSON_VALUE, SPRING_DATA_COMPACT_JSON_VALUE, TEXT_URI_LIST_VALUE }) public ResponseEntity createPropertyReference( - final RootResourceInformation resourceInformation, final HttpMethod requestMethod, final @RequestBody( - required = false) Resources incoming, @BackendId Serializable id, @PathVariable String property) - throws Exception { + final RootResourceInformation resourceInformation, final HttpMethod requestMethod, + final @RequestBody(required = false) Resources incoming, @BackendId Serializable id, + @PathVariable String property) throws Exception { final Resources source = incoming == null ? new Resources(Collections.emptyList()) : incoming; final RepositoryInvoker invoker = resourceInformation.getInvoker(); @@ -303,8 +300,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro if (prop.property.isCollectionLike()) { - Collection collection = AUGMENTING_METHODS.contains(requestMethod) ? (Collection) prop.propertyValue - : CollectionFactory.createCollection(propertyType, 0); + Collection collection = AUGMENTING_METHODS.contains(requestMethod) + ? (Collection) prop.propertyValue : CollectionFactory.createCollection(propertyType, 0); // Add to the existing collection for (Link l : source.getLinks()) { @@ -315,7 +312,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro } else if (prop.property.isMap()) { - Map map = AUGMENTING_METHODS.contains(requestMethod) ? (Map) prop.propertyValue + Map map = AUGMENTING_METHODS.contains(requestMethod) + ? (Map) prop.propertyValue : CollectionFactory. createMap(propertyType, 0); // Add to the existing collection @@ -357,7 +355,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = DELETE) public ResponseEntity deletePropertyReferenceId(final RootResourceInformation repoRequest, @BackendId Serializable id, @PathVariable String property, final @PathVariable String propertyId) - throws Exception { + throws Exception { final RepositoryInvoker invoker = repoRequest.getInvoker(); @@ -417,7 +415,10 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro String href = link.expand().getHref(); String id = href.substring(href.lastIndexOf('/') + 1); - return conversionService.convert(id, type); + + RepositoryInvoker invoker = repositoryInvokerFactory.getInvokerFor(type); + + return invoker.invokeFindOne(id); } private ResourceSupport doWithReferencedProperty(RootResourceInformation resourceInformation, Serializable id, @@ -452,7 +453,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro final Object propertyValue; final PersistentPropertyAccessor accessor; - private ReferencedProperty(PersistentProperty property, Object propertyValue, PersistentPropertyAccessor wrapper) { + private ReferencedProperty(PersistentProperty property, Object propertyValue, + PersistentPropertyAccessor wrapper) { this.property = property; this.propertyValue = propertyValue; diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java index 66c63fe37..43a4f44f3 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java @@ -27,7 +27,6 @@ import java.util.Map.Entry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; -import org.springframework.data.auditing.AuditableBeanWrapperFactory; import org.springframework.data.domain.Sort; import org.springframework.data.repository.query.Param; import org.springframework.data.repository.support.RepositoryInvoker; @@ -84,13 +83,12 @@ class RepositorySearchController extends AbstractRepositoryRestController { * @param assembler must not be {@literal null}. * @param entityLinks must not be {@literal null}. * @param mappings must not be {@literal null}. - * @param auditableBeanWrapperFactory must not be {@literal null}. */ @Autowired public RepositorySearchController(PagedResourcesAssembler assembler, RepositoryEntityLinks entityLinks, - ResourceMappings mappings, AuditableBeanWrapperFactory auditableBeanWrapperFactory) { + ResourceMappings mappings) { - super(assembler, auditableBeanWrapperFactory); + super(assembler); Assert.notNull(entityLinks, "EntityLinks must not be null!"); Assert.notNull(mappings, "ResourceMappings must not be null!"); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java new file mode 100644 index 000000000..00a0048a8 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceControllerUnitTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016 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.data.rest.webmvc; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.data.keyvalue.core.mapping.KeyValuePersistentEntity; +import org.springframework.data.keyvalue.core.mapping.context.KeyValueMappingContext; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.repository.support.RepositoryInvoker; +import org.springframework.data.repository.support.RepositoryInvokerFactory; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.data.rest.core.mapping.PersistentEntitiesResourceMappings; +import org.springframework.data.rest.core.mapping.ResourceMappings; +import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.data.rest.core.mapping.ResourceType; +import org.springframework.data.rest.core.mapping.SupportedHttpMethods; +import org.springframework.data.web.PagedResourcesAssembler; +import org.springframework.hateoas.Link; +import org.springframework.hateoas.Resources; +import org.springframework.http.HttpMethod; + +/** + * Unit tests for {@link RepositoryPropertyReferenceController}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class RepositoryPropertyReferenceControllerUnitTests { + + @Mock Repositories repositories; + @Mock PagedResourcesAssembler assembler; + @Mock RepositoryInvokerFactory invokerFactory; + @Mock RepositoryInvoker invoker; + @Mock ApplicationEventPublisher publisher; + + KeyValueMappingContext mappingContext = new KeyValueMappingContext(); + + /** + * @see DATAREST-791 + */ + @Test + public void usesRepositoryInvokerToLookupRelatedInstance() throws Exception { + + KeyValuePersistentEntity entity = mappingContext.getPersistentEntity(Sample.class); + + ResourceMappings mappings = new PersistentEntitiesResourceMappings( + new PersistentEntities(Collections.singleton(mappingContext))); + ResourceMetadata metadata = spy(mappings.getMetadataFor(Sample.class)); + when(metadata.getSupportedHttpMethods()).thenReturn(AllSupportedHttpMethods.INSTANCE); + + RepositoryPropertyReferenceController controller = new RepositoryPropertyReferenceController(repositories, + invokerFactory, assembler); + controller.setApplicationEventPublisher(publisher); + + doReturn(invoker).when(invokerFactory).getInvokerFor(Reference.class); + doReturn(new Sample()).when(invoker).invokeFindOne(4711); + doReturn(new Reference()).when(invoker).invokeFindOne("some-id"); + doReturn(new Sample()).when(invoker).invokeSave(any(Object.class)); + + RootResourceInformation information = new RootResourceInformation(metadata, entity, invoker); + Resources request = new Resources(Collections.emptySet(), new Link("/reference/some-id")); + + controller.createPropertyReference(information, HttpMethod.POST, request, 4711, "references"); + + verify(invokerFactory).getInvokerFor(Reference.class); + verify(invoker).invokeFindOne("some-id"); + } + + @RestResource + static class Sample { + @org.springframework.data.annotation.Reference List references = new ArrayList(); + } + + @RestResource + static class Reference {} + + static enum AllSupportedHttpMethods implements SupportedHttpMethods { + + INSTANCE; + + private static final Set ALL_METHODS = new HashSet(Arrays.asList(HttpMethod.values())); + + @Override + public Set getMethodsFor(PersistentProperty property) { + return ALL_METHODS; + } + + @Override + public Set getMethodsFor(ResourceType type) { + return ALL_METHODS; + } + } +}