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.
This commit is contained in:
@@ -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<Object> 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<Object> pagedResourcesAssembler,
|
||||
AuditableBeanWrapperFactory auditableBeanWrapperFactory) {
|
||||
public AbstractRepositoryRestController(PagedResourcesAssembler<Object> 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<Object> entities, PersistentEntityResourceAssembler assembler,
|
||||
@@ -146,56 +136,6 @@ class AbstractRepositoryRestController {
|
||||
return new Resources<Resource<Object>>(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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<Object> 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!");
|
||||
|
||||
@@ -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<Object>(headers, HttpStatus.NO_CONTENT);
|
||||
@@ -350,7 +351,7 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
List<String> 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<Resource<?>>(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<Resource<?>>(responseHeaders, HttpStatus.NOT_MODIFIED);
|
||||
}
|
||||
if (headersPreparer.isObjectStillValid(domainObj, headers)) {
|
||||
return new ResponseEntity<Resource<?>>(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);
|
||||
|
||||
@@ -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<HttpMethod> 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<Object> assembler, AuditableBeanWrapperFactory auditableBeanWrapperFactory) {
|
||||
RepositoryInvokerFactory repositoryInvokerFactory, PagedResourcesAssembler<Object> 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<ResourceSupport> 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<ResourceSupport> followPropertyReferenceCompact(RootResourceInformation repoRequest,
|
||||
@BackendId Serializable id, @PathVariable String property, PersistentEntityResourceAssembler assembler)
|
||||
throws Exception {
|
||||
throws Exception {
|
||||
|
||||
ResponseEntity<ResourceSupport> 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<? extends ResourceSupport> createPropertyReference(
|
||||
final RootResourceInformation resourceInformation, final HttpMethod requestMethod, final @RequestBody(
|
||||
required = false) Resources<Object> incoming, @BackendId Serializable id, @PathVariable String property)
|
||||
throws Exception {
|
||||
final RootResourceInformation resourceInformation, final HttpMethod requestMethod,
|
||||
final @RequestBody(required = false) Resources<Object> incoming, @BackendId Serializable id,
|
||||
@PathVariable String property) throws Exception {
|
||||
|
||||
final Resources<Object> source = incoming == null ? new Resources<Object>(Collections.emptyList()) : incoming;
|
||||
final RepositoryInvoker invoker = resourceInformation.getInvoker();
|
||||
@@ -303,8 +300,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
|
||||
|
||||
if (prop.property.isCollectionLike()) {
|
||||
|
||||
Collection<Object> collection = AUGMENTING_METHODS.contains(requestMethod) ? (Collection<Object>) prop.propertyValue
|
||||
: CollectionFactory.createCollection(propertyType, 0);
|
||||
Collection<Object> collection = AUGMENTING_METHODS.contains(requestMethod)
|
||||
? (Collection<Object>) 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<String, Object> map = AUGMENTING_METHODS.contains(requestMethod) ? (Map<String, Object>) prop.propertyValue
|
||||
Map<String, Object> map = AUGMENTING_METHODS.contains(requestMethod)
|
||||
? (Map<String, Object>) prop.propertyValue
|
||||
: CollectionFactory.<String, Object> 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<ResourceSupport> 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;
|
||||
|
||||
@@ -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<Object> 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!");
|
||||
|
||||
@@ -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<Object> 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<Object> request = new Resources<Object>(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<Reference> references = new ArrayList<Reference>();
|
||||
}
|
||||
|
||||
@RestResource
|
||||
static class Reference {}
|
||||
|
||||
static enum AllSupportedHttpMethods implements SupportedHttpMethods {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
private static final Set<HttpMethod> ALL_METHODS = new HashSet<HttpMethod>(Arrays.asList(HttpMethod.values()));
|
||||
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(PersistentProperty<?> property) {
|
||||
return ALL_METHODS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<HttpMethod> getMethodsFor(ResourceType type) {
|
||||
return ALL_METHODS;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user