DATAREST-217 - Significant overhaul of HTTP method support detection.
Refactored the way the general support for an HTTP method for the resource exported. The decision is implemented in RootResourceInformation (formerly RepositoryRestRequest). Removed request specific information from that class and introduced a HandlerMethodArgumentResolver to be able to inject HttpMethod instance into controller methods (filed https://jira.springsource.org/browse/SPR-11425 to get that support into Spring Framework itself). Generally moved away from throwing NoSuchMethodExceptions and correctly expose HttpRequestMethodNotSupportedException instead to make sure Spring MVC renders the appropriate allowed methods if possible. Removed RepositoryInvokerHandlerMethodArgumentResolver as a RepositoryInvoker can be obtained from the RootResourceInformation where necessary. Related pull request: #125.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -72,6 +72,15 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
|
||||
this.conversionService = conversionService;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindAllMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasFindAllMethod() {
|
||||
return methods.hasFindAllMethod();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesFindAll()
|
||||
@@ -115,6 +124,15 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
|
||||
return invoke(method, pageable);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasSaveMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasSaveMethod() {
|
||||
return methods.hasSaveMethod();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesSave()
|
||||
@@ -132,6 +150,15 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
|
||||
return invoke(methods.getSaveMethod(), object);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasFindOneMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasFindOneMethod() {
|
||||
return methods.hasFindOneMethod();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesFindOne()
|
||||
@@ -150,6 +177,15 @@ class ReflectionRepositoryInvoker implements RepositoryInvoker {
|
||||
return invoke(methods.getFindOneMethod(), convertId(id));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#hasDeleteMethod()
|
||||
*/
|
||||
@Override
|
||||
public boolean hasDeleteMethod() {
|
||||
return methods.hasDelete();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.invoke.RepositoryInvocationInformation#exposesDelete()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -22,11 +22,59 @@ package org.springframework.data.rest.core.invoke;
|
||||
*/
|
||||
public interface RepositoryInvocationInformation {
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to save objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasSaveMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the save method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesSave();
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to delete objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasDeleteMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the delete method.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesDelete();
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to find a single object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasFindOneMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the method to find a single object.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesFindOne();
|
||||
|
||||
/**
|
||||
* Returns whether the repository has a method to find all objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean hasFindAllMethod();
|
||||
|
||||
/**
|
||||
* Returns whether the repository exposes the method to find all objects.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
boolean exposesFindAll();
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.Resources;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
@@ -108,12 +109,6 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
|
||||
return notFound();
|
||||
}
|
||||
|
||||
@ExceptionHandler({ NoSuchMethodError.class, HttpRequestMethodNotSupportedException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<?> handleNoSuchMethod() {
|
||||
return errorResponse(null, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@ExceptionHandler({ HttpMessageNotReadableException.class, HttpMessageNotWritableException.class })
|
||||
@ResponseBody
|
||||
public ResponseEntity<ExceptionMessage> handleNotReadable(HttpMessageNotReadableException e) {
|
||||
@@ -157,6 +152,22 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
|
||||
return errorResponse(null, ex, HttpStatus.CONFLICT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send {@code 405 Method Not Allowed} and include the supported {@link HttpMethod}s in the {@code Allow} header.
|
||||
*
|
||||
* @param o_O
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler
|
||||
@ResponseBody
|
||||
public ResponseEntity<Void> handle(HttpRequestMethodNotSupportedException o_O) {
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setAllow(o_O.getSupportedHttpMethods());
|
||||
|
||||
return new ResponseEntity<Void>(headers, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
protected <T> ResponseEntity<T> notFound() {
|
||||
return notFound(null, null);
|
||||
}
|
||||
@@ -195,9 +206,9 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
|
||||
return new ResponseEntity<T>(body, hdrs, status);
|
||||
}
|
||||
|
||||
protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) {
|
||||
protected Link resourceLink(RootResourceInformation resourceLink, Resource resource) {
|
||||
|
||||
ResourceMetadata repoMapping = repoRequest.getResourceMetadata();
|
||||
ResourceMetadata repoMapping = resourceLink.getResourceMetadata();
|
||||
|
||||
Link selfLink = resource.getLink("self");
|
||||
String rel = repoMapping.getItemResourceRel();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2013 the original author or authors.
|
||||
* Copyright 2012-2014 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.
|
||||
@@ -41,21 +41,22 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha
|
||||
private static final String ERROR_MESSAGE = "Could not read an object of type %s from the request! Converter %s returned null!";
|
||||
private static final String NO_CONVERTER_FOUND = "No suitable HttpMessageConverter found to read request body into object of type %s from request with content type of %s!";
|
||||
|
||||
private final RepositoryRestRequestHandlerMethodArgumentResolver repoRequestResolver;
|
||||
private final RootResourceInformationHandlerMethodArgumentResolver repoRequestResolver;
|
||||
private final List<HttpMessageConverter<?>> messageConverters;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntityResourceHandlerMethodArgumentResolver} for the given
|
||||
* {@link HttpMessageConverter}s and {@link RepositoryRestRequestHandlerMethodArgumentResolver}..
|
||||
* {@link HttpMessageConverter}s and {@link RootResourceInformationHandlerMethodArgumentResolver}..
|
||||
*
|
||||
* @param messageConverters must not be {@literal null}.
|
||||
* @param repositoryRequestResolver must not be {@literal null}.
|
||||
*/
|
||||
public PersistentEntityResourceHandlerMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
|
||||
RepositoryRestRequestHandlerMethodArgumentResolver repositoryRequestResolver) {
|
||||
RootResourceInformationHandlerMethodArgumentResolver repositoryRequestResolver) {
|
||||
|
||||
Assert.notEmpty(messageConverters, "MessageConverters must not be null or empty!");
|
||||
Assert.notNull(repositoryRequestResolver, "RepositoryRestRequestHandlerMethodArgumentResolver must not be empty!");
|
||||
Assert
|
||||
.notNull(repositoryRequestResolver, "RootResourceInformationHandlerMethodArgumentResolver must not be empty!");
|
||||
|
||||
this.messageConverters = messageConverters;
|
||||
this.repoRequestResolver = repositoryRequestResolver;
|
||||
@@ -78,13 +79,14 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
RepositoryRestRequest repoRequest = (RepositoryRestRequest) repoRequestResolver.resolveArgument(parameter,
|
||||
mavContainer, webRequest, binderFactory);
|
||||
|
||||
RootResourceInformation resourceInformation = repoRequestResolver.resolveArgument(parameter, mavContainer,
|
||||
webRequest, binderFactory);
|
||||
|
||||
HttpServletRequest nativeRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
ServletServerHttpRequest request = new ServletServerHttpRequest(nativeRequest);
|
||||
|
||||
Class<?> domainType = repoRequest.getPersistentEntity().getType();
|
||||
Class<?> domainType = resourceInformation.getPersistentEntity().getType();
|
||||
MediaType contentType = request.getHeaders().getContentType();
|
||||
|
||||
for (HttpMessageConverter converter : messageConverters) {
|
||||
@@ -99,7 +101,7 @@ public class PersistentEntityResourceHandlerMethodArgumentResolver implements Ha
|
||||
throw new HttpMessageNotReadableException(String.format(ERROR_MESSAGE, domainType, converter));
|
||||
}
|
||||
|
||||
return new PersistentEntityResource<Object>(repoRequest.getPersistentEntity(), obj);
|
||||
return new PersistentEntityResource<Object>(resourceInformation.getPersistentEntity(), obj);
|
||||
}
|
||||
|
||||
throw new HttpMessageNotReadableException(String.format(NO_CONVERTER_FOUND, domainType, contentType));
|
||||
|
||||
@@ -53,6 +53,7 @@ import org.springframework.hateoas.Resource;
|
||||
import org.springframework.hateoas.ResourceSupport;
|
||||
import org.springframework.hateoas.Resources;
|
||||
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;
|
||||
@@ -107,25 +108,28 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
|
||||
public Resources<?> listEntities(final RepositoryRestRequest request, Pageable pageable, Sort sort)
|
||||
throws ResourceNotFoundException {
|
||||
public Resources<?> listEntities(final RootResourceInformation resourceInformation, Pageable pageable, Sort sort)
|
||||
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
|
||||
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
Iterable<?> results;
|
||||
RepositoryInvoker repoMethodInvoker = request.getRepositoryInvoker();
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.COLLECTION);
|
||||
|
||||
if (null == repoMethodInvoker) {
|
||||
RepositoryInvoker invoker = resourceInformation.getInvoker();
|
||||
|
||||
if (null == invoker) {
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
Iterable<?> results;
|
||||
|
||||
if (pageable != null) {
|
||||
results = repoMethodInvoker.invokeFindAll(pageable);
|
||||
results = invoker.invokeFindAll(pageable);
|
||||
} else {
|
||||
results = repoMethodInvoker.invokeFindAll(sort);
|
||||
results = invoker.invokeFindAll(sort);
|
||||
}
|
||||
|
||||
ResourceMetadata metadata = request.getResourceMetadata();
|
||||
ResourceMetadata metadata = resourceInformation.getResourceMetadata();
|
||||
SearchResourceMappings searchMappings = metadata.getSearchResourceMappings();
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
if (searchMappings.isExported()) {
|
||||
links.add(entityLinks.linkFor(metadata.getDomainType()).slash(searchMappings.getPath())
|
||||
@@ -141,7 +145,8 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
|
||||
"application/x-spring-data-compact+json", "text/uri-list" })
|
||||
public Resources<?> listEntitiesCompact(final RepositoryRestRequest repoRequest, Pageable pageable, Sort sort) {
|
||||
public Resources<?> listEntitiesCompact(final RootResourceInformation repoRequest, Pageable pageable, Sort sort)
|
||||
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
|
||||
|
||||
Resources<?> resources = listEntities(repoRequest, pageable, sort);
|
||||
List<Link> links = new ArrayList<Link>(resources.getLinks());
|
||||
@@ -159,14 +164,12 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST, consumes = { "application/json" })
|
||||
public ResponseEntity<ResourceSupport> createNewEntity(RepositoryRestRequest repoRequest,
|
||||
PersistentEntityResource<?> incoming) {
|
||||
public ResponseEntity<ResourceSupport> createNewEntity(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<?> incoming) throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.POST, ResourceType.COLLECTION);
|
||||
|
||||
if (!invoker.exposesSave()) {
|
||||
throw new NoSuchMethodError();
|
||||
}
|
||||
RepositoryInvoker invoker = resourceInformation.getInvoker();
|
||||
|
||||
publisher.publishEvent(new BeforeCreateEvent(incoming.getContent()));
|
||||
Object obj = invoker.invokeSave(incoming.getContent());
|
||||
@@ -181,17 +184,20 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code GET / repository}/{id}}
|
||||
* {@code GET /$repository/$id}
|
||||
*
|
||||
* @param repoRequest
|
||||
* @param resourceInformation
|
||||
* @param id
|
||||
* @return
|
||||
* @throws ResourceNotFoundException
|
||||
* @throws HttpRequestMethodNotSupportedException
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.GET)
|
||||
public ResponseEntity<Resource<?>> getSingleEntity(RepositoryRestRequest repoRequest, @PathVariable String id) {
|
||||
public ResponseEntity<Resource<?>> getSingleEntity(RootResourceInformation resourceInformation,
|
||||
@PathVariable String id) throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.ITEM);
|
||||
|
||||
RepositoryInvoker repoMethodInvoker = resourceInformation.getInvoker();
|
||||
|
||||
if (!repoMethodInvoker.exposesFindOne()) {
|
||||
return new ResponseEntity<Resource<?>>(HttpStatus.NOT_FOUND);
|
||||
@@ -207,29 +213,29 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code PUT / repository}/{id}} - Updates an existing entity or creates one at exactly that place.
|
||||
* {@code PUT /$repository/$id} - Updates an existing entity or creates one at exactly that place.
|
||||
*
|
||||
* @param request
|
||||
* @param resourceInformation
|
||||
* @param incoming
|
||||
* @param id
|
||||
* @return
|
||||
* @throws HttpRequestMethodNotSupportedException
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.PUT, consumes = { "application/json" })
|
||||
public ResponseEntity<? extends ResourceSupport> updateEntity(RepositoryRestRequest request,
|
||||
PersistentEntityResource<Object> incoming, @PathVariable String id) {
|
||||
public ResponseEntity<? extends ResourceSupport> updateEntity(RootResourceInformation resourceInformation,
|
||||
PersistentEntityResource<Object> incoming, @PathVariable String id) throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
RepositoryInvoker invoker = request.getRepositoryInvoker();
|
||||
if (!invoker.exposesSave() || !invoker.exposesFindOne()) {
|
||||
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.PUT, ResourceType.ITEM);
|
||||
|
||||
RepositoryInvoker invoker = resourceInformation.getInvoker();
|
||||
|
||||
Object domainObj = converter.convert(id, STRING_TYPE,
|
||||
TypeDescriptor.valueOf(request.getPersistentEntity().getType()));
|
||||
TypeDescriptor.valueOf(resourceInformation.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(request, incoming);
|
||||
return createNewEntity(resourceInformation, incoming);
|
||||
}
|
||||
|
||||
domainObjectMerger.merge(incoming.getContent(), domainObj);
|
||||
@@ -250,14 +256,12 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
}
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING + "/{id}", method = RequestMethod.DELETE)
|
||||
public ResponseEntity<?> deleteEntity(final RepositoryRestRequest repoRequest, @PathVariable final String id)
|
||||
public ResponseEntity<?> deleteEntity(final RootResourceInformation resourceInformation, @PathVariable final String id)
|
||||
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
|
||||
|
||||
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
|
||||
resourceInformation.verifySupportedMethod(HttpMethod.DELETE, ResourceType.ITEM);
|
||||
|
||||
if (!invoker.exposesDelete() || !invoker.exposesFindOne()) {
|
||||
throw new HttpRequestMethodNotSupportedException(RequestMethod.DELETE.toString());
|
||||
}
|
||||
RepositoryInvoker invoker = resourceInformation.getInvoker();
|
||||
|
||||
// TODO: re-enable not exposing delete method if hidden
|
||||
|
||||
@@ -274,5 +278,4 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
|
||||
|
||||
return new ResponseEntity<Object>(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -101,8 +101,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
}
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
|
||||
public ResponseEntity<ResourceSupport> followPropertyReference(final RepositoryRestRequest repoRequest,
|
||||
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException {
|
||||
public ResponseEntity<ResourceSupport> followPropertyReference(final RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException,
|
||||
HttpRequestMethodNotSupportedException {
|
||||
|
||||
final HttpHeaders headers = new HttpHeaders();
|
||||
|
||||
@@ -144,16 +145,16 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
}
|
||||
};
|
||||
|
||||
ResourceSupport responseResource = doWithReferencedProperty(repoRequest, id, property, handler);
|
||||
ResourceSupport responseResource = doWithReferencedProperty(repoRequest, id, property, handler, HttpMethod.GET);
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, responseResource);
|
||||
}
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.DELETE)
|
||||
public ResponseEntity<? extends ResourceSupport> deletePropertyReference(final RepositoryRestRequest repoRequest,
|
||||
public ResponseEntity<? extends ResourceSupport> deletePropertyReference(final RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException,
|
||||
HttpRequestMethodNotSupportedException {
|
||||
|
||||
final RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
|
||||
final RepositoryInvoker repoMethodInvoker = repoRequest.getInvoker();
|
||||
|
||||
if (!repoMethodInvoker.exposesDelete()) {
|
||||
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
|
||||
@@ -185,7 +186,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
};
|
||||
|
||||
try {
|
||||
doWithReferencedProperty(repoRequest, id, property, handler);
|
||||
doWithReferencedProperty(repoRequest, id, property, handler, HttpMethod.DELETE);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
if (iae.getCause() instanceof HttpRequestMethodNotSupportedException) {
|
||||
throw (HttpRequestMethodNotSupportedException) iae.getCause();
|
||||
@@ -197,9 +198,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.GET, produces = { "application/json",
|
||||
"application/x-spring-data-verbose+json", "application/x-spring-data-compact+json", "text/uri-list" })
|
||||
public ResponseEntity<ResourceSupport> followPropertyReference(final RepositoryRestRequest repoRequest,
|
||||
public ResponseEntity<ResourceSupport> followPropertyReference(final RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId)
|
||||
throws ResourceNotFoundException, NoSuchMethodException {
|
||||
throws ResourceNotFoundException, HttpRequestMethodNotSupportedException {
|
||||
|
||||
final HttpHeaders headers = new HttpHeaders();
|
||||
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
|
||||
@@ -243,14 +244,15 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
}
|
||||
};
|
||||
|
||||
ResourceSupport responseResource = doWithReferencedProperty(repoRequest, id, property, handler);
|
||||
ResourceSupport responseResource = doWithReferencedProperty(repoRequest, id, property, handler, HttpMethod.GET);
|
||||
return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, responseResource);
|
||||
}
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = {
|
||||
"application/x-spring-data-compact+json", "text/uri-list" })
|
||||
public ResponseEntity<ResourceSupport> followPropertyReferenceCompact(RepositoryRestRequest repoRequest,
|
||||
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException {
|
||||
public ResponseEntity<ResourceSupport> followPropertyReferenceCompact(RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property) throws ResourceNotFoundException,
|
||||
HttpRequestMethodNotSupportedException {
|
||||
|
||||
ResponseEntity<ResourceSupport> response = followPropertyReference(repoRequest, id, property);
|
||||
|
||||
@@ -299,15 +301,12 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
method = { RequestMethod.POST, RequestMethod.PUT }, //
|
||||
consumes = { "application/json", "application/x-spring-data-compact+json", "text/uri-list" })
|
||||
@ResponseBody
|
||||
public ResponseEntity<? extends ResourceSupport> createPropertyReference(final RepositoryRestRequest repoRequest,
|
||||
public ResponseEntity<? extends ResourceSupport> createPropertyReference(
|
||||
final RootResourceInformation resourceInformation, final HttpMethod requestMethod,
|
||||
final @RequestBody Resources<Object> incoming, @PathVariable String id, @PathVariable String property,
|
||||
final UriComponentsBuilder builder) throws NoSuchMethodException {
|
||||
final UriComponentsBuilder builder) throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
final RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
|
||||
|
||||
if (!invoker.exposesSave()) {
|
||||
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
final RepositoryInvoker invoker = resourceInformation.getInvoker();
|
||||
|
||||
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
|
||||
|
||||
@@ -321,7 +320,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
Collection<Object> coll = CollectionFactory.createCollection(propertyType, 0);
|
||||
|
||||
// Either load the exist collection to add to it (POST)
|
||||
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
|
||||
if (HttpMethod.POST.equals(requestMethod)) {
|
||||
coll = (Collection<Object>) prop.propertyValue;
|
||||
}
|
||||
|
||||
@@ -338,7 +337,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
Map<String, Object> m = CollectionFactory.createMap(propertyType, 0);
|
||||
|
||||
// Either load the exist collection to add to it (POST)
|
||||
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
|
||||
if (HttpMethod.POST.equals(requestMethod)) {
|
||||
m = (Map<String, Object>) prop.propertyValue;
|
||||
}
|
||||
|
||||
@@ -352,7 +351,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
|
||||
} else {
|
||||
|
||||
if (HttpMethod.POST.equals(repoRequest.getRequestMethod())) {
|
||||
if (HttpMethod.POST.equals(requestMethod)) {
|
||||
throw new IllegalStateException(
|
||||
"Cannot POST a reference to this singular property since the property type is not a List or a Map.");
|
||||
}
|
||||
@@ -374,7 +373,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
}
|
||||
};
|
||||
|
||||
doWithReferencedProperty(repoRequest, id, property, handler);
|
||||
doWithReferencedProperty(resourceInformation, id, property, handler, requestMethod);
|
||||
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Location", builder.build().toUriString());
|
||||
@@ -384,14 +383,14 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
|
||||
@RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.DELETE)
|
||||
@ResponseBody
|
||||
public ResponseEntity<ResourceSupport> deletePropertyReferenceId(final RepositoryRestRequest repoRequest,
|
||||
public ResponseEntity<ResourceSupport> deletePropertyReferenceId(final RootResourceInformation repoRequest,
|
||||
@PathVariable String id, @PathVariable String property, final @PathVariable String propertyId)
|
||||
throws NoSuchMethodException {
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
final RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
|
||||
final RepositoryInvoker invoker = repoRequest.getInvoker();
|
||||
|
||||
if (!invoker.exposesDelete()) {
|
||||
throw new NoSuchMethodError();
|
||||
if (!invoker.exposesSave()) {
|
||||
throw new HttpRequestMethodNotSupportedException(HttpMethod.DELETE.name());
|
||||
}
|
||||
|
||||
Function<ReferencedProperty, ResourceSupport> handler = new Function<ReferencedProperty, ResourceSupport>() {
|
||||
@@ -437,7 +436,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
}
|
||||
};
|
||||
|
||||
doWithReferencedProperty(repoRequest, id, property, handler);
|
||||
doWithReferencedProperty(repoRequest, id, property, handler, HttpMethod.DELETE);
|
||||
|
||||
return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT);
|
||||
}
|
||||
@@ -447,13 +446,14 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
|
||||
return converter.convert(id, STRING_TYPE, TypeDescriptor.valueOf(type));
|
||||
}
|
||||
|
||||
private ResourceSupport doWithReferencedProperty(RepositoryRestRequest repoRequest, String id, String propertyPath,
|
||||
Function<ReferencedProperty, ResourceSupport> handler) throws NoSuchMethodException {
|
||||
private ResourceSupport doWithReferencedProperty(RootResourceInformation repoRequest, String id, String propertyPath,
|
||||
Function<ReferencedProperty, ResourceSupport> handler, HttpMethod method)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
RepositoryInvoker invoker = repoRequest.getRepositoryInvoker();
|
||||
RepositoryInvoker invoker = repoRequest.getInvoker();
|
||||
|
||||
if (!invoker.exposesFindOne()) {
|
||||
throw new NoSuchMethodException();
|
||||
throw new HttpRequestMethodNotSupportedException(method.name());
|
||||
}
|
||||
|
||||
Object domainObj = invoker.invokeFindOne(id);
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2013 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 javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class RepositoryRestRequest {
|
||||
|
||||
private final NativeWebRequest request;
|
||||
private final ResourceMetadata resourceMetadata;
|
||||
private final RepositoryInvoker repoInvoker;
|
||||
private final PersistentEntity<?, ?> persistentEntity;
|
||||
|
||||
public RepositoryRestRequest(PersistentEntity<?, ?> entity, NativeWebRequest request, ResourceMetadata repoInfo,
|
||||
RepositoryInvoker invoker) {
|
||||
|
||||
this.request = request;
|
||||
this.resourceMetadata = repoInfo;
|
||||
if (resourceMetadata == null || !resourceMetadata.isExported()) {
|
||||
|
||||
this.repoInvoker = null;
|
||||
this.persistentEntity = null;
|
||||
|
||||
} else {
|
||||
this.repoInvoker = invoker;
|
||||
this.persistentEntity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
NativeWebRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
HttpMethod getRequestMethod() {
|
||||
return HttpMethod.valueOf(request.getNativeRequest(HttpServletRequest.class).getMethod());
|
||||
}
|
||||
|
||||
Class<?> getDomainType() {
|
||||
return resourceMetadata.getDomainType();
|
||||
}
|
||||
|
||||
ResourceMetadata getResourceMetadata() {
|
||||
return resourceMetadata;
|
||||
}
|
||||
|
||||
SearchResourceMappings getSearchMappings() {
|
||||
return resourceMetadata.getSearchResourceMappings();
|
||||
}
|
||||
|
||||
RepositoryInvoker getRepositoryInvoker() {
|
||||
return repoInvoker;
|
||||
}
|
||||
|
||||
PersistentEntity<?, ?> getPersistentEntity() {
|
||||
return persistentEntity;
|
||||
}
|
||||
}
|
||||
@@ -51,13 +51,13 @@ class RepositorySchemaController {
|
||||
/**
|
||||
* Exposes a JSON schema for the repository referenced.
|
||||
*
|
||||
* @param repoRequest will never be {@literal null}.
|
||||
* @param resourceInformation will never be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@RequestMapping(value = BASE_MAPPING + "/schema", method = RequestMethod.GET,
|
||||
produces = { "application/schema+json" })
|
||||
@ResponseBody
|
||||
public JsonSchema schema(RepositoryRestRequest repoRequest) {
|
||||
return jsonSchemaConverter.convert(repoRequest.getPersistentEntity().getType());
|
||||
public JsonSchema schema(RootResourceInformation resourceInformation) {
|
||||
return jsonSchemaConverter.convert(resourceInformation.getPersistentEntity().getType());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
|
||||
/**
|
||||
* Controller to lookup and execute searches on a given repository.
|
||||
@@ -90,20 +91,20 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
/**
|
||||
* Exposes links to the individual search resources exposed by the backing repository.
|
||||
*
|
||||
* @param request
|
||||
* @param resourceInformation
|
||||
* @return
|
||||
*/
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET)
|
||||
public ResourceSupport listSearches(RepositoryRestRequest request) {
|
||||
public ResourceSupport listSearches(RootResourceInformation resourceInformation) {
|
||||
|
||||
SearchResourceMappings resourceMappings = request.getSearchMappings();
|
||||
SearchResourceMappings resourceMappings = resourceInformation.getSearchMappings();
|
||||
|
||||
if (!resourceMappings.isExported()) {
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
Links queryMethodLinks = getSearchLinks(request.getDomainType());
|
||||
Links queryMethodLinks = getSearchLinks(resourceInformation.getDomainType());
|
||||
|
||||
if (queryMethodLinks.isEmpty()) {
|
||||
throw new ResourceNotFoundException();
|
||||
@@ -127,11 +128,11 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
*/
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING + "/{search}", method = RequestMethod.GET)
|
||||
public ResponseEntity<Resources<?>> executeSearch(RepositoryRestRequest request, @PathVariable String search,
|
||||
Pageable pageable) {
|
||||
public ResponseEntity<Resources<?>> executeSearch(RootResourceInformation resourceInformation, WebRequest request,
|
||||
@PathVariable String search, Pageable pageable) {
|
||||
|
||||
Method method = checkExecutability(request, search);
|
||||
Resources<?> resources = executeQueryMethod(request, method, pageable);
|
||||
Method method = checkExecutability(resourceInformation, search);
|
||||
Resources<?> resources = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable);
|
||||
|
||||
return new ResponseEntity<Resources<?>>(resources, HttpStatus.OK);
|
||||
}
|
||||
@@ -139,7 +140,7 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
/**
|
||||
* Executes a query method and exposes the results in compact form.
|
||||
*
|
||||
* @param repoRequest
|
||||
* @param resourceInformation
|
||||
* @param repository
|
||||
* @param method
|
||||
* @param pageable
|
||||
@@ -148,11 +149,11 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
@ResponseBody
|
||||
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET, //
|
||||
produces = { "application/x-spring-data-compact+json" })
|
||||
public ResourceSupport executeSearchCompact(RepositoryRestRequest repoRequest, @PathVariable String repository,
|
||||
@PathVariable String search, Pageable pageable) {
|
||||
public ResourceSupport executeSearchCompact(RootResourceInformation resourceInformation, WebRequest request,
|
||||
@PathVariable String repository, @PathVariable String search, Pageable pageable) {
|
||||
|
||||
Method method = checkExecutability(repoRequest, search);
|
||||
ResourceSupport resource = executeQueryMethod(repoRequest, method, pageable);
|
||||
Method method = checkExecutability(resourceInformation, search);
|
||||
ResourceSupport resource = executeQueryMethod(resourceInformation.getInvoker(), request, method, pageable);
|
||||
|
||||
List<Link> links = new ArrayList<Link>();
|
||||
|
||||
@@ -161,14 +162,14 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
for (Object obj : ((Resources<?>) resource).getContent()) {
|
||||
if (null != obj && obj instanceof Resource) {
|
||||
Resource<?> res = (Resource<?>) obj;
|
||||
links.add(resourceLink(repoRequest, res));
|
||||
links.add(resourceLink(resourceInformation, res));
|
||||
}
|
||||
}
|
||||
|
||||
} else if (resource instanceof Resource) {
|
||||
|
||||
Resource<?> res = (Resource<?>) resource;
|
||||
links.add(resourceLink(repoRequest, res));
|
||||
links.add(resourceLink(resourceInformation, res));
|
||||
}
|
||||
|
||||
return new Resource<Object>(EMPTY_RESOURCE_LIST, links);
|
||||
@@ -178,13 +179,13 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
* Checks that the given request is actually executable. Will reject execution if we don't find a search with the
|
||||
* given name.
|
||||
*
|
||||
* @param request
|
||||
* @param resourceInformation
|
||||
* @param searchName
|
||||
* @return
|
||||
*/
|
||||
private Method checkExecutability(RepositoryRestRequest request, String searchName) {
|
||||
private Method checkExecutability(RootResourceInformation resourceInformation, String searchName) {
|
||||
|
||||
ResourceMetadata metadata = request.getResourceMetadata();
|
||||
ResourceMetadata metadata = resourceInformation.getResourceMetadata();
|
||||
SearchResourceMappings searchMapping = metadata.getSearchResourceMappings();
|
||||
|
||||
if (!searchMapping.isExported()) {
|
||||
@@ -201,16 +202,17 @@ class RepositorySearchController extends AbstractRepositoryRestController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param repoRequest
|
||||
* @param invoker
|
||||
* @param request
|
||||
* @param method
|
||||
* @param pageable
|
||||
* @return
|
||||
*/
|
||||
private Resources<?> executeQueryMethod(final RepositoryRestRequest repoRequest, Method method, Pageable pageable) {
|
||||
private Resources<?> executeQueryMethod(final RepositoryInvoker invoker, WebRequest request, Method method,
|
||||
Pageable pageable) {
|
||||
|
||||
RepositoryInvoker repoMethodInvoker = repoRequest.getRepositoryInvoker();
|
||||
Map<String, String[]> parameters = repoRequest.getRequest().getParameterMap();
|
||||
Object result = repoMethodInvoker.invokeQueryMethod(method, parameters, pageable, null);
|
||||
Map<String, String[]> parameters = request.getParameterMap();
|
||||
Object result = invoker.invokeQueryMethod(method, parameters, pageable, null);
|
||||
|
||||
return resultToResources(result);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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 org.springframework.http.HttpStatus;
|
||||
@@ -7,6 +22,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
* Indicates a resource was not found.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public class ResourceNotFoundException extends RuntimeException {
|
||||
@@ -14,11 +30,11 @@ public class ResourceNotFoundException extends RuntimeException {
|
||||
private static final long serialVersionUID = 7992904489502842099L;
|
||||
|
||||
public ResourceNotFoundException() {
|
||||
super("Resource not found!");
|
||||
this("Resource not found!");
|
||||
}
|
||||
|
||||
public ResourceNotFoundException(String message) {
|
||||
super(message);
|
||||
this(message, null);
|
||||
}
|
||||
|
||||
public ResourceNotFoundException(String message, Throwable cause) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2014 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;
|
||||
|
||||
/**
|
||||
* An enum listing all supported resource types.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public enum ResourceType {
|
||||
|
||||
COLLECTION, ITEM;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2012-2014 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 java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
|
||||
/**
|
||||
* Meta-information about the root repository resource.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
class RootResourceInformation {
|
||||
|
||||
private final ResourceMetadata resourceMetadata;
|
||||
private final RepositoryInvoker invoker;
|
||||
private final PersistentEntity<?, ?> persistentEntity;
|
||||
|
||||
public RootResourceInformation(ResourceMetadata metadata, PersistentEntity<?, ?> entity, RepositoryInvoker invoker) {
|
||||
|
||||
this.resourceMetadata = metadata;
|
||||
if (resourceMetadata == null || !resourceMetadata.isExported()) {
|
||||
|
||||
this.invoker = null;
|
||||
this.persistentEntity = null;
|
||||
|
||||
} else {
|
||||
this.invoker = invoker;
|
||||
this.persistentEntity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
public Class<?> getDomainType() {
|
||||
return resourceMetadata.getDomainType();
|
||||
}
|
||||
|
||||
public ResourceMetadata getResourceMetadata() {
|
||||
return resourceMetadata;
|
||||
}
|
||||
|
||||
public SearchResourceMappings getSearchMappings() {
|
||||
return resourceMetadata.getSearchResourceMappings();
|
||||
}
|
||||
|
||||
public RepositoryInvoker getInvoker() {
|
||||
return invoker;
|
||||
}
|
||||
|
||||
public PersistentEntity<?, ?> getPersistentEntity() {
|
||||
return persistentEntity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the supported {@link HttpMethod}s for the given {@link ResourceType}.
|
||||
*
|
||||
* @param resourcType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Collection<HttpMethod> getSupportedMethods(ResourceType resourcType) {
|
||||
|
||||
Assert.notNull(resourcType, "Resource type must not be null!");
|
||||
|
||||
if (invoker == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<HttpMethod> methods = new HashSet<HttpMethod>();
|
||||
|
||||
switch (resourcType) {
|
||||
case COLLECTION:
|
||||
|
||||
if (invoker.exposesFindAll()) {
|
||||
methods.add(HttpMethod.GET);
|
||||
}
|
||||
|
||||
if (invoker.exposesSave()) {
|
||||
methods.add(HttpMethod.POST);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ITEM:
|
||||
|
||||
if (invoker.exposesDelete() && invoker.hasFindOneMethod()) {
|
||||
methods.add(HttpMethod.DELETE);
|
||||
}
|
||||
|
||||
if (invoker.exposesFindOne()) {
|
||||
methods.add(HttpMethod.GET);
|
||||
}
|
||||
|
||||
if (invoker.exposesSave()) {
|
||||
methods.add(HttpMethod.PUT);
|
||||
methods.add(HttpMethod.PATCH);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Unsupported resource type %s!", resourcType));
|
||||
}
|
||||
|
||||
return Collections.unmodifiableSet(methods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link HttpMethod} is supported for the given {@link ResourceType}.
|
||||
*
|
||||
* @param httpMethod must not be {@literal null}.
|
||||
* @param resourceType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean supports(HttpMethod httpMethod, ResourceType resourceType) {
|
||||
|
||||
Assert.notNull(httpMethod, "HTTP method must not be null!");
|
||||
Assert.notNull(resourceType, "Resource type must not be null!");
|
||||
|
||||
return getSupportedMethods(resourceType).contains(httpMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the given {@link HttpMethod} is supported for the given {@link ResourceType}.
|
||||
*
|
||||
* @param httpMethod must not be {@literal null}.
|
||||
* @param resourceType must not be {@literal null}.
|
||||
* @throws HttpRequestMethodNotSupportedException if the {@link ResourceType} does not support the given
|
||||
* {@link HttpMethod}. Will contain all supported methods as indicators for clients.
|
||||
*/
|
||||
public void verifySupportedMethod(HttpMethod httpMethod, ResourceType resourceType)
|
||||
throws HttpRequestMethodNotSupportedException {
|
||||
|
||||
Assert.notNull(httpMethod, "HTTP method must not be null!");
|
||||
Assert.notNull(resourceType, "Resource type must not be null!");
|
||||
|
||||
Collection<HttpMethod> supportedMethods = getSupportedMethods(resourceType);
|
||||
|
||||
if (!supportedMethods.contains(httpMethod)) {
|
||||
|
||||
Set<String> stringMethods = new HashSet<String>();
|
||||
|
||||
for (HttpMethod supportedMethod : supportedMethods) {
|
||||
stringMethods.add(supportedMethod.name());
|
||||
}
|
||||
|
||||
throw new HttpRequestMethodNotSupportedException(httpMethod.name(), stringMethods);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,24 +28,27 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to create {@link RootResourceInformation} for injection into Spring MVC
|
||||
* controller methods.
|
||||
*
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RepositoryRestRequestHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
public class RootResourceInformationHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final RepositoryInvokerFactory invokerFactory;
|
||||
private final ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoryRestRequestHandlerMethodArgumentResolver} using the given {@link Repositories},
|
||||
* Creates a new {@link RootResourceInformationHandlerMethodArgumentResolver} using the given {@link Repositories},
|
||||
* {@link RepositoryInvokerFactory} and {@link ResourceMetadataHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param invokerFactory must not be {@literal null}.
|
||||
* @param resourceMetadataResolver must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryRestRequestHandlerMethodArgumentResolver(Repositories repositories,
|
||||
public RootResourceInformationHandlerMethodArgumentResolver(Repositories repositories,
|
||||
RepositoryInvokerFactory invokerFactory, ResourceMetadataHandlerMethodArgumentResolver resourceMetadataResolver) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
@@ -63,7 +66,7 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return RepositoryRestRequest.class.isAssignableFrom(parameter.getParameterType());
|
||||
return RootResourceInformation.class.isAssignableFrom(parameter.getParameterType());
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -71,17 +74,17 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
|
||||
*/
|
||||
@Override
|
||||
public RepositoryRestRequest resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
public RootResourceInformation resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
ResourceMetadata repoInfo = resourceMetadataResolver.resolveArgument(parameter, mavContainer, webRequest,
|
||||
ResourceMetadata resourceMetadata = resourceMetadataResolver.resolveArgument(parameter, mavContainer, webRequest,
|
||||
binderFactory);
|
||||
|
||||
RepositoryInvoker repositoryInvoker = invokerFactory.getInvokerFor(repoInfo.getDomainType());
|
||||
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(repoInfo.getDomainType());
|
||||
Class<?> domainType = resourceMetadata.getDomainType();
|
||||
RepositoryInvoker repositoryInvoker = invokerFactory.getInvokerFor(domainType);
|
||||
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(domainType);
|
||||
|
||||
// TODO reject if ResourceMetadata cannot be resolved
|
||||
|
||||
return new RepositoryRestRequest(persistentEntity, webRequest, repoInfo, repositoryInvoker);
|
||||
return new RootResourceInformation(resourceMetadata, persistentEntity, repositoryInvoker);
|
||||
}
|
||||
}
|
||||
@@ -51,13 +51,14 @@ import org.springframework.data.rest.webmvc.PersistentEntityResourceHandlerMetho
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestRequestHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.RootResourceInformationHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.ResourceMetadataHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.ServerHttpRequestMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper;
|
||||
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module;
|
||||
import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter;
|
||||
import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.JpaHelper;
|
||||
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
|
||||
import org.springframework.data.rest.webmvc.support.ValidationExceptionHandler;
|
||||
@@ -230,8 +231,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() {
|
||||
return new RepositoryRestRequestHandlerMethodArgumentResolver(repositories(), repositoryInvokerFactory(),
|
||||
public RootResourceInformationHandlerMethodArgumentResolver repoRequestArgumentResolver() {
|
||||
return new RootResourceInformationHandlerMethodArgumentResolver(repositories(), repositoryInvokerFactory(),
|
||||
resourceMetadataHandlerMethodArgumentResolver());
|
||||
}
|
||||
|
||||
@@ -474,7 +475,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
|
||||
return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(),
|
||||
repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
|
||||
resourceMetadataHandlerMethodArgumentResolver());
|
||||
resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE);
|
||||
}
|
||||
|
||||
private ObjectMapper basicObjectMapper() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2014 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.
|
||||
@@ -13,33 +13,26 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.rest.webmvc;
|
||||
package org.springframework.data.rest.webmvc.support;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to provide {@link HttpMethod} instances for innjection into MVC controller
|
||||
* methods.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class RepositoryInvokerHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
public enum HttpMethodHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final RepositoryRestRequestHandlerMethodArgumentResolver requestResolver;
|
||||
private final RepositoryInvokerFactory invokerFactory;
|
||||
|
||||
/**
|
||||
* @param requestResolver
|
||||
* @param invokerFactory
|
||||
*/
|
||||
private RepositoryInvokerHandlerMethodArgumentResolver(
|
||||
RepositoryRestRequestHandlerMethodArgumentResolver requestResolver, RepositoryInvokerFactory invokerFactory) {
|
||||
this.requestResolver = requestResolver;
|
||||
this.invokerFactory = invokerFactory;
|
||||
}
|
||||
INSTANCE;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -47,17 +40,18 @@ public class RepositoryInvokerHandlerMethodArgumentResolver implements HandlerMe
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return RepositoryInvoker.class.isAssignableFrom(parameter.getParameterType());
|
||||
return HttpMethod.class.equals(parameter.getParameterType());
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
|
||||
*/
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
public HttpMethod resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
RepositoryRestRequest request = requestResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
|
||||
return invokerFactory.getInvokerFor(request.getPersistentEntity().getType());
|
||||
HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
return HttpMethod.valueOf(httpServletRequest.getMethod().trim().toUpperCase());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -32,6 +32,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
|
||||
/**
|
||||
* Base class to write integration tests for controllers.
|
||||
@@ -54,29 +55,30 @@ public abstract class AbstractControllerIntegrationTests {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link RepositoryRestRequest} for the given domain type.
|
||||
* Returns a {@link RootResourceInformation} for the given domain type.
|
||||
*
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected RepositoryRestRequest getRequest(Class<?> domainType) {
|
||||
return getRequest(domainType, RequestParameters.NONE);
|
||||
}
|
||||
|
||||
protected RepositoryRestRequest getRequest(Class<?> domainType, RequestParameters parameters) {
|
||||
protected RootResourceInformation getResourceInformation(Class<?> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "Domain type must not be null!");
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(domainType);
|
||||
|
||||
return new RootResourceInformation(mappings.getMappingFor(domainType), entity,
|
||||
invokerFactory.getInvokerFor(domainType));
|
||||
}
|
||||
|
||||
protected WebRequest getRequest(RequestParameters parameters) {
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setParameters(parameters.asMap());
|
||||
|
||||
ServletRequestAttributes requestAttributes = new ServletRequestAttributes(request);
|
||||
RequestContextHolder.setRequestAttributes(requestAttributes);
|
||||
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(domainType);
|
||||
|
||||
return new RepositoryRestRequest(entity, new ServletWebRequest(request), mappings.getMappingFor(domainType),
|
||||
invokerFactory.getInvokerFor(domainType));
|
||||
return new ServletWebRequest(request);
|
||||
}
|
||||
|
||||
protected ResourceMetadata getMetadata(Class<?> domainType) {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2014 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 org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.rest.webmvc.jpa.Address;
|
||||
import org.springframework.data.rest.webmvc.jpa.AddressRepository;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link RepositoryEntityController}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@ContextConfiguration(classes = JpaRepositoryConfig.class)
|
||||
@Transactional
|
||||
public class RepositoryEntityControllerIntegrationTests extends AbstractControllerIntegrationTests {
|
||||
|
||||
@Autowired RepositoryEntityController controller;
|
||||
@Autowired AddressRepository repository;
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test(expected = HttpRequestMethodNotSupportedException.class)
|
||||
public void returnsNotFoundForListingEntitiesIfFindAllNotExported() throws Exception {
|
||||
|
||||
repository.save(new Address());
|
||||
|
||||
RootResourceInformation request = getResourceInformation(Address.class);
|
||||
controller.listEntities(request, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test(expected = HttpRequestMethodNotSupportedException.class)
|
||||
public void rejectsEntityCreationIfSaveIsNotExported() throws Exception {
|
||||
|
||||
RootResourceInformation request = getResourceInformation(Address.class);
|
||||
|
||||
controller.createNewEntity(request, null);
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ public class RepositoryRestHandlerMappingUnitTests {
|
||||
|
||||
mockRequest = new MockHttpServletRequest();
|
||||
|
||||
listEntitiesMethod = RepositoryEntityController.class.getMethod("listEntities", RepositoryRestRequest.class,
|
||||
listEntitiesMethod = RepositoryEntityController.class.getMethod("listEntities", RootResourceInformation.class,
|
||||
Pageable.class, Sort.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013 the original author or authors.
|
||||
* Copyright 2013-2014 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.
|
||||
@@ -55,7 +55,7 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
@Test
|
||||
public void rendersCorrectSearchLinksForPersons() {
|
||||
|
||||
RepositoryRestRequest request = getRequest(Person.class);
|
||||
RootResourceInformation request = getResourceInformation(Person.class);
|
||||
ResourceSupport resource = controller.listSearches(request);
|
||||
|
||||
ResourceTester tester = ResourceTester.of(resource);
|
||||
@@ -70,24 +70,23 @@ public class RepositorySearchControllerIntegrationTests extends AbstractControll
|
||||
@Test(expected = ResourceNotFoundException.class)
|
||||
public void returns404ForUnexportedRepository() {
|
||||
|
||||
RepositoryRestRequest request = getRequest(CreditCard.class);
|
||||
controller.listSearches(request);
|
||||
controller.listSearches(getResourceInformation(CreditCard.class));
|
||||
}
|
||||
|
||||
@Test(expected = ResourceNotFoundException.class)
|
||||
public void returns404ForRepositoryWithoutSearches() {
|
||||
|
||||
RepositoryRestRequest request = getRequest(Order.class);
|
||||
controller.listSearches(request);
|
||||
controller.listSearches(getResourceInformation(Order.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void executesSearchAgainstRepository() {
|
||||
|
||||
RequestParameters parameters = new RequestParameters("firstName", "John");
|
||||
RepositoryRestRequest request = getRequest(Person.class, parameters);
|
||||
RootResourceInformation resourceInformation = getResourceInformation(Person.class);
|
||||
|
||||
ResponseEntity<Resources<?>> response = controller.executeSearch(request, "firstname", null);
|
||||
ResponseEntity<Resources<?>> response = controller.executeSearch(resourceInformation, getRequest(parameters),
|
||||
"firstname", null);
|
||||
|
||||
ResourceTester tester = ResourceTester.of(response.getBody());
|
||||
PagedResources<Object> pagedResources = tester.assertIsPage();
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2014 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.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.data.rest.webmvc.jpa.Address;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link RootResourceInformation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = JpaRepositoryConfig.class)
|
||||
@Transactional
|
||||
public class RootResourceInformationIntegrationTests extends AbstractControllerIntegrationTests {
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void getIsNotSupportedIfFindAllIsNotExported() {
|
||||
|
||||
RootResourceInformation information = getResourceInformation(Address.class);
|
||||
assertThat(information.supports(HttpMethod.GET, ResourceType.COLLECTION), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void postIsNotSupportedIfSaveIsNotExported() {
|
||||
|
||||
RootResourceInformation information = getResourceInformation(Address.class);
|
||||
assertThat(information.supports(HttpMethod.POST, ResourceType.COLLECTION), is(false));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2014 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.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.springframework.data.rest.webmvc.ResourceType.*;
|
||||
import static org.springframework.http.HttpMethod.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link RootResourceInformation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class RootResourceInformationUnitTests {
|
||||
|
||||
@Mock ResourceMetadata metadata;
|
||||
@Mock PersistentEntity<?, ?> entity;
|
||||
RepositoryInvoker invoker;
|
||||
|
||||
RootResourceInformation information;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
when(metadata.isExported()).thenReturn(true);
|
||||
this.invoker = mock(RepositoryInvoker.class, new DefaultBooleanToTrue());
|
||||
this.information = new RootResourceInformation(metadata, entity, invoker);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void defaultsSupportedHttpMethodsForItemResource() {
|
||||
|
||||
assertThat(information.getSupportedMethods(ResourceType.ITEM), hasItems(GET, PUT, PATCH, DELETE));
|
||||
assertThat(information.getSupportedMethods(ResourceType.ITEM), not(hasItems(POST)));
|
||||
|
||||
assertThat(information.getSupportedMethods(COLLECTION), hasItems(GET, POST));
|
||||
assertThat(information.getSupportedMethods(COLLECTION), not(hasItems(PUT, PATCH, DELETE)));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportGetOnItemResourceIfFindOneIsNotExported() {
|
||||
|
||||
when(invoker.exposesFindOne()).thenReturn(false);
|
||||
assertThat(information.supports(GET, ITEM), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportDeleteOnItemResourceIfDeleteIsNotExported() {
|
||||
|
||||
when(invoker.exposesDelete()).thenReturn(false);
|
||||
assertThat(information.supports(DELETE, ITEM), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotSupportPutOnItemResourceIfSaveIsNotExported() {
|
||||
|
||||
when(invoker.exposesSave()).thenReturn(false);
|
||||
assertThat(information.supports(POST, ITEM), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to default boolean methods to return {@literal true} instead of {@literal false} by default.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class DefaultBooleanToTrue implements Answer<Object> {
|
||||
|
||||
private static final Answer<Object> DEFAULT = Mockito.RETURNS_DEFAULTS;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.mockito.stubbing.Answer#answer(org.mockito.invocation.InvocationOnMock)
|
||||
*/
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||
|
||||
Class<?> returnType = invocation.getMethod().getReturnType();
|
||||
return returnType.equals(Boolean.class) || returnType.equals(boolean.class) ? true : DEFAULT.answer(invocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2014 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.jpa;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Entity
|
||||
public class Address {
|
||||
|
||||
@Id @GeneratedValue Long id;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2014 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.jpa;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface AddressRepository extends CrudRepository<Address, Long> {
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
Iterable<Address> findAll();
|
||||
|
||||
@Override
|
||||
@RestResource(exported = false)
|
||||
<S extends Address> S save(S entity);
|
||||
}
|
||||
@@ -364,6 +364,30 @@ public class JpaWebTests extends AbstractWebIntegrationTests {
|
||||
return links;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotAllowGetToCollectionResourceIfFindAllIsNotExported() throws Exception {
|
||||
|
||||
Link link = discoverUnique("addresses");
|
||||
|
||||
mvc.perform(get(link.getHref())).//
|
||||
andExpect(status().isMethodNotAllowed());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-217
|
||||
*/
|
||||
@Test
|
||||
public void doesNotAllowPostToCollectionResourceIfSaveIsNotExported() throws Exception {
|
||||
|
||||
Link link = discoverUnique("addresses");
|
||||
|
||||
mvc.perform(post(link.getHref()).content("{}").contentType(MediaType.APPLICATION_JSON)).//
|
||||
andExpect(status().isMethodNotAllowed());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts the {@link Person} resource the given link points to contains siblings with the given names.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user