DATAREST-103 - Added support for non-CrudRepositor implementations.

The exporter can now work with repositories that do not implement CrudRepository. Introduced a new RepositoryInvoker abstraction that transparently exposes which CRUD methods the repository offers. 

We still offer special invokers that can be used for CrudRepository and PagingaAndSortingRepository implementations to avoid the reflection overhead the general mechanism introduces.
This commit is contained in:
Oliver Gierke
2013-07-17 15:14:45 +02:00
parent a4d8a22428
commit 975b333746
16 changed files with 591 additions and 588 deletions

View File

@@ -197,7 +197,7 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) {
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMetadata repoMapping = repoRequest.getResourceMetadata();
Link selfLink = resource.getLink("self");
String rel = repoMapping.getSingleResourceRel();

View File

@@ -17,7 +17,6 @@ package org.springframework.data.rest.webmvc;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import java.io.Serializable;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
@@ -30,6 +29,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
@@ -42,7 +42,7 @@ import org.springframework.data.rest.repository.context.AfterSaveEvent;
import org.springframework.data.rest.repository.context.BeforeCreateEvent;
import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.repository.mapping.SearchResourceMappings;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
@@ -55,9 +55,6 @@ import org.springframework.hateoas.Resources;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionOperations;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -80,8 +77,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
private final ConversionService conversionService;
private final DomainObjectMerger domainObjectMerger;
private final TransactionOperations txOperations;
private ApplicationEventPublisher publisher;
@Autowired
@@ -98,8 +93,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
this.converter = converter;
this.conversionService = conversionService;
this.domainObjectMerger = domainObjectMerger;
this.txOperations = null;
}
/*
@@ -114,33 +107,28 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
public Resources<?> listEntities(final RepositoryRestRequest request, Pageable pageable)
public Resources<?> listEntities(final RepositoryRestRequest request, Pageable pageable, Sort sort)
throws ResourceNotFoundException {
List<Link> links = new ArrayList<Link>();
Iterable<?> results;
RepositoryMethodInvoker repoMethodInvoker = request.getRepositoryMethodInvoker();
RepositoryInvoker repoMethodInvoker = request.getRepositoryInvoker();
if (null == repoMethodInvoker) {
throw new ResourceNotFoundException();
}
if (repoMethodInvoker.hasFindAllPageable()) {
results = repoMethodInvoker.findAll(pageable);
} else if (repoMethodInvoker.hasFindAllSorted()) {
results = repoMethodInvoker.findAll(pageable.getSort());
} else if (repoMethodInvoker.hasFindAll()) {
results = repoMethodInvoker.findAll();
if (pageable != null) {
results = repoMethodInvoker.invokeFindAll(pageable);
} else {
throw new ResourceNotFoundException();
results = repoMethodInvoker.invokeFindAll(sort);
}
ResourceMetadata repoMapping = request.getRepositoryResourceMapping();
SearchResourceMappings searchMappings = repoMapping.getSearchResourceMappings();
ResourceMetadata metadata = request.getResourceMetadata();
SearchResourceMappings searchMappings = metadata.getSearchResourceMappings();
if (searchMappings.isExported()) {
links.add(entityLinks.linkFor(repoMapping.getDomainType()).slash(searchMappings.getPath())
links.add(entityLinks.linkFor(metadata.getDomainType()).slash(searchMappings.getPath())
.withRel(searchMappings.getRel()));
}
@@ -149,13 +137,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
return resources;
}
@ResponseBody
@SuppressWarnings({ "unchecked" })
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
"application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public Resources<?> listEntitiesCompact(final RepositoryRestRequest repoRequest, Pageable pageable)
throws ResourceNotFoundException {
Resources<?> resources = listEntities(repoRequest, pageable);
public Resources<?> listEntitiesCompact(final RepositoryRestRequest repoRequest, Pageable pageable, Sort sort) {
Resources<?> resources = listEntities(repoRequest, pageable, sort);
List<Link> links = new ArrayList<Link>(resources.getLinks());
for (Resource<?> resource : ((Resources<Resource<?>>) resources).getContent()) {
@@ -169,18 +157,19 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
}
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST, consumes = { "application/json" }, produces = {
"application/json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> createNewEntity(RepositoryRestRequest repoRequest,
PersistentEntityResource<?> incoming) {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasSaveOne()) {
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesSave()) {
throw new NoSuchMethodError();
}
publisher.publishEvent(new BeforeCreateEvent(incoming.getContent()));
Object obj = repoMethodInvoker.save(incoming.getContent());
Object obj = invoker.invokeSave(incoming.getContent());
publisher.publishEvent(new AfterCreateEvent(obj));
Link selfLink = perAssembler.getSelfLinkFor(obj);
@@ -202,59 +191,58 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
* @return
* @throws ResourceNotFoundException
*/
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-compact+json", "text/uri-list" })
@ResponseBody
public Resource<?> getSingleEntity(RepositoryRestRequest repoRequest, @PathVariable String id)
throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasFindOne()) {
throw new ResourceNotFoundException();
public ResponseEntity<Resource<?>> getSingleEntity(RepositoryRestRequest repoRequest, @PathVariable String id) {
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
if (!repoMethodInvoker.exposesFindOne()) {
return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
}
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
Object domainObj = repoMethodInvoker.invokeFindOne(id);
if (null == domainObj) {
throw new ResourceNotFoundException();
if (domainObj == null) {
return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
}
return perAssembler.toResource(domainObj);
return new ResponseEntity<Resource<?>>(perAssembler.toResource(domainObj), HttpStatus.OK);
}
/**
* {@code PUT / repository}/{id}} - Updates an existing entity or creates one at exactly that place.
*
* @param repoRequest
* @param request
* @param incoming
* @param id
* @return
* @throws ResourceNotFoundException
*/
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT, consumes = { "application/json" },
produces = { "application/json", "text/uri-list" })
@ResponseBody
public ResponseEntity<Resource<?>> updateEntity(RepositoryRestRequest repoRequest,
PersistentEntityResource<Object> incoming, @PathVariable String id) throws ResourceNotFoundException {
public ResponseEntity<Resource<?>> updateEntity(RepositoryRestRequest request,
PersistentEntityResource<Object> incoming, @PathVariable String id) {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasSaveOne() || !repoMethodInvoker.hasFindOne()) {
throw new NoSuchMethodError();
RepositoryInvoker invoker = request.getRepositoryInvoker();
if (!invoker.exposesSave() || !invoker.exposesFindOne()) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
TypeDescriptor.valueOf(request.getPersistentEntity().getType()));
if (null == domainObj) {
BeanWrapper<?, Object> incomingWrapper = BeanWrapper.create(incoming.getContent(), conversionService);
PersistentProperty<?> idProp = incoming.getPersistentEntity().getIdProperty();
incomingWrapper.setProperty(idProp, conversionService.convert(id, idProp.getType()));
return createNewEntity(repoRequest, incoming);
return createNewEntity(request, incoming);
}
domainObjectMerger.merge(incoming.getContent(), domainObj);
publisher.publishEvent(new BeforeSaveEvent(incoming.getContent()));
Object obj = repoMethodInvoker.save(domainObj);
Object obj = invoker.invokeSave(domainObj);
publisher.publishEvent(new AfterSaveEvent(obj));
if (config.isReturnBodyOnUpdate()) {
@@ -268,10 +256,11 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
@ResponseBody
public ResponseEntity<?> deleteEntity(final RepositoryRestRequest repoRequest, @PathVariable final String id)
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (null == repoMethodInvoker || !repoMethodInvoker.hasFindOne()
&& !(repoMethodInvoker.hasDeleteOne() || repoMethodInvoker.hasDeleteOneById())) {
throw new HttpRequestMethodNotSupportedException("DELETE");
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesDelete() || !invoker.exposesFindOne()) {
throw new HttpRequestMethodNotSupportedException(RequestMethod.DELETE.toString());
}
// TODO: re-enable not exposing delete method if hidden
@@ -281,36 +270,10 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
// throw new HttpRequestMethodNotSupportedException("DELETE");
// }
final Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));
if (null == domainObj) {
throw new ResourceNotFoundException();
}
Object domainObj = invoker.invokeFindOne(id);
publisher.publishEvent(new BeforeDeleteEvent(domainObj));
TransactionCallbackWithoutResult callback = new TransactionCallbackWithoutResult() {
@Override
@SuppressWarnings({ "unchecked" })
protected void doInTransactionWithoutResult(TransactionStatus status) {
if (repoMethodInvoker.hasDeleteOneById()) {
Class<? extends Serializable> idType = (Class<? extends Serializable>) repoRequest.getPersistentEntity()
.getIdProperty().getType();
final Serializable idVal = conversionService.convert(id, idType);
repoMethodInvoker.delete(idVal);
} else if (repoMethodInvoker.hasDeleteOne()) {
repoMethodInvoker.delete(domainObj);
}
}
};
// FIXME
if (txOperations != null) {
txOperations.execute(callback);
} else {
callback.doInTransaction(null);
}
invoker.invokeDelete(id);
publisher.publishEvent(new AfterDeleteEvent(domainObj));
return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);

View File

@@ -43,12 +43,13 @@ import org.springframework.data.rest.repository.context.AfterLinkDeleteEvent;
import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
import org.springframework.data.rest.repository.context.BeforeLinkDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -149,9 +150,11 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
public ResponseEntity<Resource<?>> deletePropertyReference(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException,
HttpRequestMethodNotSupportedException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasDeleteOne()) {
throw new NoSuchMethodException();
final RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
if (!repoMethodInvoker.exposesDelete()) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@@ -169,7 +172,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
Object result = repoMethodInvoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
@@ -245,7 +248,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
return response;
}
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMetadata repoMapping = repoRequest.getResourceMetadata();
PersistentProperty<?> persistentProp = repoRequest.getPersistentEntity().getPersistentProperty(property);
Class<?> propType = persistentProp.isCollectionLike() || persistentProp.isMap() ? persistentProp.getComponentType()
@@ -281,17 +284,19 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
@ResponseBody
public ResponseEntity<Resource<?>> createPropertyReference(final RepositoryRestRequest repoRequest,
final @RequestBody Resource<Object> incoming, @PathVariable String id, @PathVariable String property)
throws ResourceNotFoundException, NoSuchMethodException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasSaveOne()) {
throw new NoSuchMethodException();
throws NoSuchMethodException {
final RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesSave()) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@Override
public Resource<?> apply(ReferencedProperty prop) {
if (prop.property.isCollectionLike()) {
Collection<Object> coll = new ArrayList<Object>();
if ("POST".equals(repoRequest.getRequest().getMethod())) {
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
coll.addAll((Collection<Object>) prop.propertyValue);
}
for (Link l : incoming.getLinks()) {
@@ -301,7 +306,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
prop.wrapper.setProperty(prop.property, coll);
} else if (prop.property.isMap()) {
Map<String, Object> m = new HashMap<String, Object>();
if ("POST".equals(repoRequest.getRequest().getMethod())) {
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
m.putAll((Map<String, Object>) prop.propertyValue);
}
for (Link l : incoming.getLinks()) {
@@ -310,7 +315,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
prop.wrapper.setProperty(prop.property, m);
} else {
if ("POST".equals(repoRequest.getRequest().getMethod())) {
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
throw new IllegalStateException(
"Cannot POST a reference to this singular property since the property type is not a List or a Map.");
}
@@ -323,12 +328,14 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
publisher.publishEvent(new BeforeLinkSaveEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
Object result = invoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkSaveEvent(result, prop.propertyValue));
return null;
}
};
doWithReferencedProperty(repoRequest, id, property, handler);
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.CREATED);
}
@@ -336,10 +343,12 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
@ResponseBody
public ResponseEntity<Resource<?>> deletePropertyReferenceId(final RepositoryRestRequest repoRequest,
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId)
throws ResourceNotFoundException, NoSuchMethodException {
final RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasDeleteOne()) {
throw new NoSuchMethodException();
throws NoSuchMethodException {
final RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
if (!invoker.exposesDelete()) {
throw new NoSuchMethodError();
}
Function<ReferencedProperty, Resource<?>> handler = new Function<ReferencedProperty, Resource<?>>() {
@@ -373,11 +382,12 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
publisher.publishEvent(new BeforeLinkDeleteEvent(prop.wrapper.getBean(), prop.propertyValue));
Object result = repoMethodInvoker.save(prop.wrapper.getBean());
Object result = invoker.invokeSave(prop.wrapper.getBean());
publisher.publishEvent(new AfterLinkDeleteEvent(result, prop.propertyValue));
return null;
}
};
doWithReferencedProperty(repoRequest, id, property, handler);
return ControllerUtils.toResponseEntity(null, EMPTY_RESOURCE, HttpStatus.NO_CONTENT);
@@ -395,9 +405,11 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
}
private Resource<?> doWithReferencedProperty(RepositoryRestRequest repoRequest, String id, String propertyPath,
Function<ReferencedProperty, Resource<?>> handler) throws ResourceNotFoundException, NoSuchMethodException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (!repoMethodInvoker.hasFindOne()) {
Function<ReferencedProperty, Resource<?>> handler) throws NoSuchMethodException {
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
if (!repoMethodInvoker.exposesFindOne()) {
throw new NoSuchMethodException();
}

View File

@@ -15,19 +15,15 @@
*/
package org.springframework.data.rest.webmvc;
import java.io.Serializable;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.http.HttpMethod;
import org.springframework.web.context.request.NativeWebRequest;
/**
* @author Jon Brisbin
@@ -35,48 +31,47 @@ import org.springframework.data.rest.repository.mapping.ResourceMetadata;
*/
class RepositoryRestRequest {
private final HttpServletRequest request;
private final NativeWebRequest request;
private final URI baseUri;
private final ResourceMetadata resourceMetadata;
private final RepositoryMethodInvoker repoMethodInvoker;
private final RepositoryInvoker repoInvoker;
private final PersistentEntity<?, ?> persistentEntity;
public RepositoryRestRequest(RepositoryRestConfiguration config, Repositories repositories,
HttpServletRequest request, URI baseUri, ResourceMetadata repoInfo, ConversionService conversionService) {
public RepositoryRestRequest(PersistentEntity<?, ?> entity, NativeWebRequest request, URI baseUri,
ResourceMetadata repoInfo, RepositoryInvoker invoker) {
this.request = request;
this.baseUri = baseUri;
this.resourceMetadata = repoInfo;
if (resourceMetadata == null || !resourceMetadata.isExported()) {
this.repoMethodInvoker = null;
this.repoInvoker = null;
this.persistentEntity = null;
} else {
Class<?> domainType = repoInfo.getDomainType();
CrudRepository<Object, Serializable> repositoryFor = repositories.getRepositoryFor(domainType);
RepositoryInformation information = repositories.getRepositoryInformationFor(domainType);
this.repoMethodInvoker = new RepositoryMethodInvoker(repositoryFor, information, conversionService);
this.persistentEntity = repositories.getPersistentEntity(domainType);
this.repoInvoker = invoker;
this.persistentEntity = entity;
}
}
HttpServletRequest getRequest() {
NativeWebRequest getRequest() {
return request;
}
HttpMethod getRequestMethod() {
return HttpMethod.valueOf(request.getNativeRequest(HttpServletRequest.class).getMethod());
}
URI getBaseUri() {
return baseUri;
}
ResourceMetadata getRepositoryResourceMapping() {
ResourceMetadata getResourceMetadata() {
return resourceMetadata;
}
RepositoryMethodInvoker getRepositoryMethodInvoker() {
return repoMethodInvoker;
RepositoryInvoker getRepositoryInvoker() {
return repoInvoker;
}
PersistentEntity<?, ?> getPersistentEntity() {

View File

@@ -17,14 +17,14 @@ package org.springframework.data.rest.webmvc;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
@@ -36,15 +36,29 @@ import org.springframework.web.method.support.ModelAndViewContainer;
*/
public class RepositoryRestRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
private final ConversionService conversionService;
private final Repositories repositories;
private final RepositoryInvokerFactory invokerFactory;
private final ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver;
private final BaseUriMethodArgumentResolver baseUriResolver;
@Autowired private RepositoryRestConfiguration config;
@Autowired private Repositories repositories;
@Autowired private ResourceMetadataHandlerMethodArgumentResolver repoInfoResolver;
@Autowired private BaseUriMethodArgumentResolver baseUriResolver;
/**
* Creates a new {@link RepositoryRestRequestHandlerMethodArgumentResolver} using the given {@link Repositories} and
* {@link ConversionService}.
*
* @param repositories must not be {@literal null}.
* @param conversionService must not be {@literal null}.
*/
public RepositoryRestRequestHandlerMethodArgumentResolver(Repositories repositories,
ConversionService conversionService, ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver,
BaseUriMethodArgumentResolver baseUriResolver) {
public RepositoryRestRequestHandlerMethodArgumentResolver(ConversionService conversionService) {
this.conversionService = conversionService;
Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(conversionService, "ConversionService must not be null!");
this.repositories = repositories;
this.invokerFactory = new RepositoryInvokerFactory(repositories, conversionService);
this.resourceMetadataResolver = resourceMetadataResolver;
this.baseUriResolver = baseUriResolver;
}
/*
@@ -65,11 +79,14 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
URI baseUri = baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
ResourceMetadata repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
ResourceMetadata repoInfo = resourceMetadataResolver.resolveArgument(parameter, mavContainer, webRequest,
binderFactory);
RepositoryInvoker repositoryInvoker = invokerFactory.getInvokerFor(repoInfo.getDomainType());
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(repoInfo.getDomainType());
// TODO reject if ResourceMetadata cannot be resolved
return new RepositoryRestRequest(config, repositories, webRequest.getNativeRequest(HttpServletRequest.class),
baseUri, repoInfo, conversionService);
return new RepositoryRestRequest(persistentEntity, webRequest, baseUri, repoInfo, repositoryInvoker);
}
}

View File

@@ -26,8 +26,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.rest.repository.invoke.RepositoryMethod;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.invoke.RepositoryInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMapping;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
@@ -39,6 +38,8 @@ import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.ResourceSupport;
import org.springframework.hateoas.Resources;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@@ -98,40 +99,39 @@ class RepositorySearchController extends AbstractRepositoryRestController {
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
@ResponseBody
public ResourceSupport query(final RepositoryRestRequest repoRequest, @PathVariable String repository,
@PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
public ResponseEntity<ResourceSupport> query(final RepositoryRestRequest repoRequest,
@PathVariable String repository, @PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
ResourceMetadata metadata = repoRequest.getResourceMetadata();
SearchResourceMappings searchMapping = metadata.getSearchResourceMappings();
if (repoMethodInvoker.getQueryMethods().isEmpty()) {
throw new ResourceNotFoundException();
if (searchMapping.isExported()) {
return new ResponseEntity<ResourceSupport>(HttpStatus.NOT_FOUND);
}
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
SearchResourceMappings searchResourceMappings = repoMapping.getSearchResourceMappings();
Method mappedMethod = searchResourceMappings.getMappedMethod(method);
Method mappedMethod = searchMapping.getMappedMethod(method);
if (mappedMethod == null) {
throw new ResourceNotFoundException();
return new ResponseEntity<ResourceSupport>(HttpStatus.NOT_FOUND);
}
RepositoryMethod repositoryMethod = new RepositoryMethod(mappedMethod);
Map<String, String[]> parameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(mappedMethod, parameters, pageable, null);
Map<String, String[]> rawParameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(repositoryMethod, pageable, rawParameters);
return resultToResources(result);
return new ResponseEntity<ResourceSupport>(resultToResources(result), HttpStatus.OK);
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET,
produces = { "application/x-spring-data-compact+json" })
@ResponseBody
public ResourceSupport queryCompact(RepositoryRestRequest repoRequest, @PathVariable String repository,
@PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
@PathVariable String method, Pageable pageable) {
List<Link> links = new ArrayList<Link>();
ResourceSupport resource = query(repoRequest, repository, method, pageable);
ResponseEntity<ResourceSupport> entity = query(repoRequest, repository, method, pageable);
ResourceSupport resource = entity.getBody();
links.addAll(resource.getLinks());
if (resource instanceof Resources && ((Resources<?>) resource).getContent() != null) {
@@ -148,5 +148,4 @@ class RepositorySearchController extends AbstractRepositoryRestController {
return new Resource<Object>(EMPTY_RESOURCE_LIST, links);
}
}

View File

@@ -213,7 +213,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
*/
@Bean
public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() {
return new RepositoryRestRequestHandlerMethodArgumentResolver(defaultConversionService());
return new RepositoryRestRequestHandlerMethodArgumentResolver(repositories(), defaultConversionService(),
resourceMetadataHandlerMethodArgumentResolver(), baseUriMethodArgumentResolver());
}
@Bean