diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvoker.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvoker.java index 09e5c1768..b6b1a2d01 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvoker.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvoker.java @@ -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() diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvocationInformation.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvocationInformation.java index f716ac19a..637986666 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvocationInformation.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvocationInformation.java @@ -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(); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java index ec52a80fd..422bf9890 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java @@ -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 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 handle(HttpRequestMethodNotSupportedException o_O) { + + HttpHeaders headers = new HttpHeaders(); + headers.setAllow(o_O.getSupportedHttpMethods()); + + return new ResponseEntity(headers, HttpStatus.METHOD_NOT_ALLOWED); + } + protected ResponseEntity notFound() { return notFound(null, null); } @@ -195,9 +206,9 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi return new ResponseEntity(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(); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java index 4bc1b1a3d..c45472580 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/PersistentEntityResourceHandlerMethodArgumentResolver.java @@ -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> 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> 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(repoRequest.getPersistentEntity(), obj); + return new PersistentEntityResource(resourceInformation.getPersistentEntity(), obj); } throw new HttpMessageNotReadableException(String.format(NO_CONVERTER_FOUND, domainType, contentType)); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java index 83f11ede3..b85622383 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java @@ -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 links = new ArrayList(); - 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 links = new ArrayList(); 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 links = new ArrayList(resources.getLinks()); @@ -159,14 +164,12 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem @ResponseBody @RequestMapping(value = BASE_MAPPING, method = RequestMethod.POST, consumes = { "application/json" }) - public ResponseEntity createNewEntity(RepositoryRestRequest repoRequest, - PersistentEntityResource incoming) { + public ResponseEntity 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> getSingleEntity(RepositoryRestRequest repoRequest, @PathVariable String id) { + public ResponseEntity> 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>(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 updateEntity(RepositoryRestRequest request, - PersistentEntityResource incoming, @PathVariable String id) { + public ResponseEntity updateEntity(RootResourceInformation resourceInformation, + PersistentEntityResource incoming, @PathVariable String id) throws HttpRequestMethodNotSupportedException { - RepositoryInvoker invoker = request.getRepositoryInvoker(); - if (!invoker.exposesSave() || !invoker.exposesFindOne()) { - return new ResponseEntity>(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 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(HttpStatus.NO_CONTENT); } - } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java index 745b8291c..dfbb8964b 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryPropertyReferenceController.java @@ -101,8 +101,9 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes } @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET) - public ResponseEntity followPropertyReference(final RepositoryRestRequest repoRequest, - @PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException { + public ResponseEntity 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 deletePropertyReference(final RepositoryRestRequest repoRequest, + public ResponseEntity 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>(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 followPropertyReference(final RepositoryRestRequest repoRequest, + public ResponseEntity 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 handler = new Function() { @@ -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 followPropertyReferenceCompact(RepositoryRestRequest repoRequest, - @PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, NoSuchMethodException { + public ResponseEntity followPropertyReferenceCompact(RootResourceInformation repoRequest, + @PathVariable String id, @PathVariable String property) throws ResourceNotFoundException, + HttpRequestMethodNotSupportedException { ResponseEntity 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 createPropertyReference(final RepositoryRestRequest repoRequest, + public ResponseEntity createPropertyReference( + final RootResourceInformation resourceInformation, final HttpMethod requestMethod, final @RequestBody Resources 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>(HttpStatus.METHOD_NOT_ALLOWED); - } + final RepositoryInvoker invoker = resourceInformation.getInvoker(); Function handler = new Function() { @@ -321,7 +320,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes Collection 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) prop.propertyValue; } @@ -338,7 +337,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes Map 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) 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 deletePropertyReferenceId(final RepositoryRestRequest repoRequest, + public ResponseEntity 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 handler = new Function() { @@ -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 handler) throws NoSuchMethodException { + private ResourceSupport doWithReferencedProperty(RootResourceInformation repoRequest, String id, String propertyPath, + Function 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); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequest.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequest.java deleted file mode 100644 index 044f637bf..000000000 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequest.java +++ /dev/null @@ -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; - } -} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySchemaController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySchemaController.java index 9f7b52fac..3b9f3c2e6 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySchemaController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySchemaController.java @@ -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()); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java index c85bc0b1f..3b88eb48b 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositorySearchController.java @@ -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> executeSearch(RepositoryRestRequest request, @PathVariable String search, - Pageable pageable) { + public ResponseEntity> 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, 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 links = new ArrayList(); @@ -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(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 parameters = repoRequest.getRequest().getParameterMap(); - Object result = repoMethodInvoker.invokeQueryMethod(method, parameters, pageable, null); + Map parameters = request.getParameterMap(); + Object result = invoker.invokeQueryMethod(method, parameters, pageable, null); return resultToResources(result); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceNotFoundException.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceNotFoundException.java index fbc4f2c98..85afe5364 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceNotFoundException.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceNotFoundException.java @@ -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) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceType.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceType.java new file mode 100644 index 000000000..72c51b13b --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceType.java @@ -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; +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RootResourceInformation.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RootResourceInformation.java new file mode 100644 index 000000000..2d8174746 --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RootResourceInformation.java @@ -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 getSupportedMethods(ResourceType resourcType) { + + Assert.notNull(resourcType, "Resource type must not be null!"); + + if (invoker == null) { + return Collections.emptySet(); + } + + Set methods = new HashSet(); + + 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 supportedMethods = getSupportedMethods(resourceType); + + if (!supportedMethods.contains(httpMethod)) { + + Set stringMethods = new HashSet(); + + for (HttpMethod supportedMethod : supportedMethods) { + stringMethods.add(supportedMethod.name()); + } + + throw new HttpRequestMethodNotSupportedException(httpMethod.name(), stringMethods); + } + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequestHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RootResourceInformationHandlerMethodArgumentResolver.java similarity index 77% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequestHandlerMethodArgumentResolver.java rename to spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RootResourceInformationHandlerMethodArgumentResolver.java index 765fca83d..b89a95517 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequestHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RootResourceInformationHandlerMethodArgumentResolver.java @@ -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); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index 89782d8b8..03f8ae594 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -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 defaultMethodArgumentResolvers() { return Arrays.asList(pageableResolver(), sortResolver(), serverHttpRequestMethodArgumentResolver(), repoRequestArgumentResolver(), persistentEntityArgumentResolver(), - resourceMetadataHandlerMethodArgumentResolver()); + resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE); } private ObjectMapper basicObjectMapper() { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInvokerHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/HttpMethodHandlerMethodArgumentResolver.java similarity index 57% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInvokerHandlerMethodArgumentResolver.java rename to spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/HttpMethodHandlerMethodArgumentResolver.java index 15fee0f0b..3cfca17e9 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInvokerHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/support/HttpMethodHandlerMethodArgumentResolver.java @@ -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()); } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java index b1059dff3..2abd72ee8 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/AbstractControllerIntegrationTests.java @@ -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) { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java new file mode 100644 index 000000000..5d3af02b5 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryEntityControllerIntegrationTests.java @@ -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); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java index def2a908c..024a389b5 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMappingUnitTests.java @@ -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); } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java index da0f6eb97..bfd64e73a 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositorySearchControllerIntegrationTests.java @@ -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> response = controller.executeSearch(request, "firstname", null); + ResponseEntity> response = controller.executeSearch(resourceInformation, getRequest(parameters), + "firstname", null); ResourceTester tester = ResourceTester.of(response.getBody()); PagedResources pagedResources = tester.assertIsPage(); diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RootResourceInformationIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RootResourceInformationIntegrationTests.java new file mode 100644 index 000000000..ee96e36e6 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RootResourceInformationIntegrationTests.java @@ -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)); + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RootResourceInformationUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RootResourceInformationUnitTests.java new file mode 100644 index 000000000..cd7819824 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RootResourceInformationUnitTests.java @@ -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 { + + private static final Answer 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); + } + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Address.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Address.java new file mode 100644 index 000000000..086ce4563 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Address.java @@ -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; +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/AddressRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/AddressRepository.java new file mode 100644 index 000000000..fbaed5355 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/AddressRepository.java @@ -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 { + + @Override + @RestResource(exported = false) + Iterable
findAll(); + + @Override + @RestResource(exported = false) + S save(S entity); +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java index ea321d8ed..f02278e97 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/JpaWebTests.java @@ -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. *