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; + } + } +}