diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java index 0417c260c..f0a28339d 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -15,12 +15,16 @@ */ package org.springframework.data.rest.core.mapping; +import static org.springframework.data.rest.core.mapping.ResourceType.*; +import static org.springframework.http.HttpMethod.*; + import java.lang.reflect.Method; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.repository.core.CrudMethods; import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.http.HttpMethod; @@ -48,19 +52,6 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods { this.exposedMethods = new DefaultExposureAwareCrudMethods(crudMethods); } - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#supports(org.springframework.http.HttpMethod, org.springframework.data.rest.core.mapping.ResourceType) - */ - @Override - public boolean supports(HttpMethod method, ResourceType type) { - - Assert.notNull(method, "HTTP method must not be null!"); - Assert.notNull(type, "Resource type must not be null!"); - - return getMethodsFor(type).contains(method); - } - /* * (non-Javadoc) * @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getSupportedHttpMethods(org.springframework.data.rest.core.mapping.ResourceType) @@ -71,18 +62,18 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods { Assert.notNull(resourcType, "Resource type must not be null!"); Set methods = new HashSet(); - methods.add(HttpMethod.OPTIONS); + methods.add(OPTIONS); switch (resourcType) { case COLLECTION: if (exposedMethods.exposesFindAll()) { - methods.add(HttpMethod.GET); - methods.add(HttpMethod.HEAD); + methods.add(GET); + methods.add(HEAD); } if (exposedMethods.exposesSave()) { - methods.add(HttpMethod.POST); + methods.add(POST); } break; @@ -90,17 +81,17 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods { case ITEM: if (exposedMethods.exposesDelete() && exposedMethods.exposesFindOne()) { - methods.add(HttpMethod.DELETE); + methods.add(DELETE); } if (exposedMethods.exposesFindOne()) { - methods.add(HttpMethod.GET); - methods.add(HttpMethod.HEAD); + methods.add(GET); + methods.add(HEAD); } if (exposedMethods.exposesSave()) { - methods.add(HttpMethod.PUT); - methods.add(HttpMethod.PATCH); + methods.add(PUT); + methods.add(PATCH); } break; @@ -112,6 +103,34 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods { return Collections.unmodifiableSet(methods); } + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.mapping.PersistentProperty) + */ + @Override + public Set getMethodsFor(PersistentProperty property) { + + if (!property.isAssociation()) { + return Collections.emptySet(); + } + + Set methods = new HashSet(); + + methods.add(GET); + + if (property.isWritable() && getMethodsFor(ITEM).contains(PUT)) { + methods.add(PUT); + methods.add(PATCH); + methods.add(DELETE); + } + + if (property.isCollectionLike() && property.isWritable()) { + methods.add(POST); + } + + return methods; + } + /** * @author Oliver Gierke */ diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SupportedHttpMethods.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SupportedHttpMethods.java index 69503609d..487fe4b0b 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SupportedHttpMethods.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SupportedHttpMethods.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -18,6 +18,7 @@ package org.springframework.data.rest.core.mapping; import java.util.Collections; import java.util.Set; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.http.HttpMethod; /** @@ -27,22 +28,21 @@ import org.springframework.http.HttpMethod; */ public interface SupportedHttpMethods { - /** - * 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 - */ - boolean supports(HttpMethod method, ResourceType type); - /** * Returns the supported {@link HttpMethod}s for the given {@link ResourceType}. * - * @param resourcType must not be {@literal null}. + * @param type must not be {@literal null}. * @return */ - Set getMethodsFor(ResourceType resourcType); + Set getMethodsFor(ResourceType type); + + /** + * Returns the supported {@link HttpMethod}s for the given {@link PersistentProperty}. + * + * @param property must not be {@literal null}. + * @return + */ + Set getMethodsFor(PersistentProperty property); /** * Null object to abstract the absence of any support for any HTTP method. @@ -64,11 +64,11 @@ public interface SupportedHttpMethods { /* * (non-Javadoc) - * @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#supports(org.springframework.http.HttpMethod, org.springframework.data.rest.core.mapping.ResourceType) + * @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getMethodsFor(org.springframework.data.mapping.PersistentProperty) */ @Override - public boolean supports(HttpMethod method, ResourceType type) { - return false; + public Set getMethodsFor(PersistentProperty property) { + return Collections.emptySet(); } } } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java index e864a745c..44ab9c246 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -15,15 +15,21 @@ */ package org.springframework.data.rest.core.mapping; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; import static org.springframework.data.rest.core.mapping.ResourceType.*; import static org.springframework.http.HttpMethod.*; -import org.hamcrest.Matcher; +import java.util.List; +import java.util.Set; + import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.annotation.ReadOnlyProperty; +import org.springframework.data.annotation.Reference; +import org.springframework.data.mongodb.core.mapping.MongoMappingContext; +import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.Repository; import org.springframework.data.repository.core.CrudMethods; @@ -91,6 +97,30 @@ public class CrudMethodsSupportedHttpMethodsUnitTests { assertMethodsSupported(supportedHttpMethods, ITEM, false, DELETE); } + /** + * @see DATAREST-523 + */ + @Test + public void exposesMethodsForProperties() { + + MongoMappingContext context = new MongoMappingContext(); + MongoPersistentEntity entity = context.getPersistentEntity(Entity.class); + + SupportedHttpMethods methods = getSupportedHttpMethodsFor(EntityRepository.class); + + assertThat(methods.getMethodsFor(entity.getPersistentProperty("embedded")), is(empty())); + assertThat(methods.getMethodsFor(entity.getPersistentProperty("embeddedCollection")), is(empty())); + + assertThat(methods.getMethodsFor(entity.getPersistentProperty("related")), + allOf(hasItems(GET, DELETE, PATCH, PUT), not(hasItem(POST)))); + + assertThat(methods.getMethodsFor(entity.getPersistentProperty("relatedCollection")), + hasItems(GET, DELETE, PATCH, PUT, POST)); + + assertThat(methods.getMethodsFor(entity.getPersistentProperty("readOnlyReference")), + allOf(hasItem(GET), not(hasItems(DELETE, PATCH, PUT, POST)))); + } + private static SupportedHttpMethods getSupportedHttpMethodsFor(Class repositoryInterface) { RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); @@ -102,12 +132,12 @@ public class CrudMethodsSupportedHttpMethodsUnitTests { private static void assertMethodsSupported(SupportedHttpMethods methods, ResourceType type, boolean supported, HttpMethod... httpMethods) { - Matcher> isSupported = supported ? hasItems(httpMethods) : not(hasItems(httpMethods)); + Set result = methods.getMethodsFor(type); - assertThat(methods.getMethodsFor(type), isSupported); + assertThat(result, supported ? hasItems(httpMethods) : not(hasItems(httpMethods))); - for (HttpMethod method : httpMethods) { - assertThat(methods.supports(method, type), is(supported)); + if (supported) { + assertThat(result, hasSize(httpMethods.length)); } } @@ -120,4 +150,15 @@ public class CrudMethodsSupportedHttpMethodsUnitTests { @RestResource(exported = false) void delete(Object id); } + + interface EntityRepository extends CrudRepository {} + + class Entity { + + Entity embedded; + @Reference Entity related; + List embeddedCollection; + @Reference List relatedCollection; + @ReadOnlyProperty @Reference Entity readOnlyReference; + } } 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 a06d37956..f856cba9f 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -16,10 +16,13 @@ package org.springframework.data.rest.webmvc; import static org.springframework.data.rest.webmvc.ControllerUtils.*; +import static org.springframework.data.rest.webmvc.RestMediaTypes.*; import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*; +import static org.springframework.web.bind.annotation.RequestMethod.*; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -48,7 +51,6 @@ import org.springframework.data.rest.core.event.BeforeLinkSaveEvent; import org.springframework.data.rest.core.mapping.PropertyAwareResourceMapping; import org.springframework.data.rest.core.mapping.ResourceMapping; import org.springframework.data.rest.core.mapping.ResourceMetadata; -import org.springframework.data.rest.core.mapping.ResourceType; import org.springframework.data.rest.core.util.Function; import org.springframework.data.rest.webmvc.support.BackendId; import org.springframework.data.web.PagedResourcesAssembler; @@ -60,13 +62,12 @@ import org.springframework.hateoas.mvc.ControllerLinkBuilder; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; /** * @author Jon Brisbin @@ -79,6 +80,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro ApplicationEventPublisherAware { private static final String BASE_MAPPING = "/{repository}/{id}/{property}"; + private static final Collection AUGMENTING_METHODS = Arrays.asList(HttpMethod.PATCH, HttpMethod.POST); private final Repositories repositories; private final ConversionService conversionService; @@ -105,7 +107,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro this.publisher = applicationEventPublisher; } - @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET) + @RequestMapping(value = BASE_MAPPING, method = GET) public ResponseEntity followPropertyReference(final RootResourceInformation repoRequest, @BackendId Serializable id, final @PathVariable String property, final PersistentEntityResourceAssembler assembler) throws Exception { @@ -148,17 +150,12 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, responseResource); } - @RequestMapping(value = BASE_MAPPING, method = RequestMethod.DELETE) + @RequestMapping(value = BASE_MAPPING, method = DELETE) public ResponseEntity deletePropertyReference(final RootResourceInformation repoRequest, @BackendId Serializable id, @PathVariable String property) throws Exception { final RepositoryInvoker repoMethodInvoker = repoRequest.getInvoker(); - // Can't delete a property if - if (repoRequest.getSupportedMethods().supports(HttpMethod.PUT, ResourceType.ITEM)) { - return new ResponseEntity>(HttpStatus.METHOD_NOT_ALLOWED); - } - Function handler = new Function() { @Override @@ -189,7 +186,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } - @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.GET) + @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = GET) public ResponseEntity followPropertyReference(final RootResourceInformation repoRequest, @BackendId Serializable id, @PathVariable String property, final @PathVariable String propertyId, final PersistentEntityResourceAssembler assembler) throws Exception { @@ -229,6 +226,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro } else { return new Resource(prop.propertyValue); } + throw new ResourceNotFoundException(); } }; @@ -237,8 +235,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro return ControllerUtils.toResponseEntity(HttpStatus.OK, headers, responseResource); } - @RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { - "application/x-spring-data-compact+json", "text/uri-list" }) + @RequestMapping(value = BASE_MAPPING, method = GET, + produces = { SPRING_DATA_COMPACT_JSON_VALUE, TEXT_URI_LIST_VALUE }) public ResponseEntity followPropertyReferenceCompact(RootResourceInformation repoRequest, @BackendId Serializable id, @PathVariable String property, PersistentEntityResourceAssembler assembler) throws Exception { @@ -286,10 +284,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro return ControllerUtils.toResponseEntity(HttpStatus.OK, null, new Resource(EMPTY_RESOURCE_LIST, links)); } - @RequestMapping(value = BASE_MAPPING, // - method = { RequestMethod.PATCH, RequestMethod.PUT }, // - consumes = { "application/json", "application/x-spring-data-compact+json", "text/uri-list" }) - @ResponseBody + @RequestMapping(value = BASE_MAPPING, method = { PATCH, PUT, POST }, // + consumes = { MediaType.APPLICATION_JSON_VALUE, SPRING_DATA_COMPACT_JSON_VALUE, TEXT_URI_LIST_VALUE }) public ResponseEntity createPropertyReference( final RootResourceInformation resourceInformation, final HttpMethod requestMethod, final @RequestBody( required = false) Resources incoming, @BackendId Serializable id, @PathVariable String property) @@ -307,37 +303,27 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro if (prop.property.isCollectionLike()) { - Collection coll = CollectionFactory.createCollection(propertyType, 0); - - // Either load the exist collection to add to it (PATCH) - if (HttpMethod.PATCH.equals(requestMethod)) { - coll = (Collection) prop.propertyValue; - } + Collection collection = AUGMENTING_METHODS.contains(requestMethod) ? (Collection) prop.propertyValue + : CollectionFactory.createCollection(propertyType, 0); // Add to the existing collection for (Link l : source.getLinks()) { - Object propVal = loadPropertyValue(prop.propertyType, l); - coll.add(propVal); + collection.add(loadPropertyValue(prop.propertyType, l)); } - prop.accessor.setProperty(prop.property, coll); + prop.accessor.setProperty(prop.property, collection); } else if (prop.property.isMap()) { - Map m = CollectionFactory.createMap(propertyType, 0); - - // Either load the exist collection to add to it (PATCH) - if (HttpMethod.PATCH.equals(requestMethod)) { - m = (Map) prop.propertyValue; - } + Map map = AUGMENTING_METHODS.contains(requestMethod) ? (Map) prop.propertyValue + : CollectionFactory. createMap(propertyType, 0); // Add to the existing collection for (Link l : source.getLinks()) { - Object propVal = loadPropertyValue(prop.propertyType, l); - m.put(l.getRel(), propVal); + map.put(l.getRel(), loadPropertyValue(prop.propertyType, l)); } - prop.accessor.setProperty(prop.property, m); + prop.accessor.setProperty(prop.property, map); } else { @@ -368,19 +354,13 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro return ControllerUtils.toEmptyResponse(HttpStatus.NO_CONTENT); } - @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = RequestMethod.DELETE) - @ResponseBody + @RequestMapping(value = BASE_MAPPING + "/{propertyId}", method = DELETE) public ResponseEntity deletePropertyReferenceId(final RootResourceInformation repoRequest, @BackendId Serializable id, @PathVariable String property, final @PathVariable String propertyId) throws Exception { final RepositoryInvoker invoker = repoRequest.getInvoker(); - // Property can't be deleted if root resource can't be updated - if (!repoRequest.getSupportedMethods().supports(HttpMethod.PUT, ResourceType.ITEM)) { - throw new HttpRequestMethodNotSupportedException(HttpMethod.DELETE.name()); - } - Function handler = new Function() { @Override @@ -443,12 +423,6 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro private ResourceSupport doWithReferencedProperty(RootResourceInformation resourceInformation, Serializable id, String propertyPath, Function handler, HttpMethod method) throws Exception { - RepositoryInvoker invoker = resourceInformation.getInvoker(); - - if (!resourceInformation.getSupportedMethods().supports(method, ResourceType.ITEM)) { - throw new HttpRequestMethodNotSupportedException(method.name()); - } - ResourceMetadata metadata = resourceInformation.getResourceMetadata(); PropertyAwareResourceMapping mapping = metadata.getProperty(propertyPath); @@ -456,13 +430,16 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro throw new ResourceNotFoundException(); } + PersistentProperty property = mapping.getProperty(); + resourceInformation.verifySupportedMethod(method, property); + + RepositoryInvoker invoker = resourceInformation.getInvoker(); Object domainObj = invoker.invokeFindOne(id); if (null == domainObj) { throw new ResourceNotFoundException(); } - PersistentProperty property = mapping.getProperty(); PersistentPropertyAccessor accessor = property.getOwner().getPropertyAccessor(domainObj); return handler.apply(new ReferencedProperty(property, accessor.getProperty(property), accessor)); } @@ -484,5 +461,4 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro this.entity = repositories.getPersistentEntity(propertyType); } } - } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestMediaTypes.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestMediaTypes.java index 7afe1fc16..2e748f032 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestMediaTypes.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RestMediaTypes.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -25,6 +25,9 @@ import org.springframework.http.MediaType; */ public class RestMediaTypes { + public static final String SPRING_DATA_COMPACT_JSON_VALUE = "application/x-spring-data-compact+json"; + public static final String TEXT_URI_LIST_VALUE = "text/uri-list"; + public static final MediaType HAL_JSON = MediaTypes.HAL_JSON; public static final MediaType JSON_PATCH_JSON = MediaType.valueOf("application/json-patch+json"); @@ -33,5 +36,7 @@ public class RestMediaTypes { public static final MediaType SCHEMA_JSON = MediaType.valueOf("application/schema+json"); public static final MediaType SPRING_DATA_VERBOSE_JSON = MediaType.valueOf("application/x-spring-data-verbose+json"); - public static final MediaType SPRING_DATA_COMPACT_JSON = MediaType.valueOf("application/x-spring-data-compact+json"); + public static final MediaType SPRING_DATA_COMPACT_JSON = MediaType.valueOf(SPRING_DATA_COMPACT_JSON_VALUE); + + public static final MediaType TEXT_URI_LIST = MediaType.valueOf(TEXT_URI_LIST_VALUE); } 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 index 567dc676e..bc36b61aa 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2014 the original author or authors. + * Copyright 2012-2015 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. @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.Set; import org.springframework.data.mapping.PersistentEntity; +import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.repository.support.RepositoryInvoker; import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.core.mapping.ResourceType; @@ -92,25 +93,57 @@ public class RootResourceInformation { public void verifySupportedMethod(HttpMethod httpMethod, ResourceType resourceType) throws HttpRequestMethodNotSupportedException, ResourceNotFoundException { + Assert.notNull(httpMethod, "HTTP method must not be null!"); + Assert.notNull(resourceType, "Resource type must not be null!"); + if (!resourceMetadata.isExported()) { throw new ResourceNotFoundException(); } - Assert.notNull(httpMethod, "HTTP method must not be null!"); - Assert.notNull(resourceType, "Resource type must not be null!"); - SupportedHttpMethods httpMethods = resourceMetadata.getSupportedHttpMethods(); Collection supportedMethods = httpMethods.getMethodsFor(resourceType); if (!supportedMethods.contains(httpMethod)) { - - Set stringMethods = new HashSet(); - - for (HttpMethod supportedMethod : supportedMethods) { - stringMethods.add(supportedMethod.name()); - } - - throw new HttpRequestMethodNotSupportedException(httpMethod.name(), stringMethods); + reject(httpMethod, supportedMethods); } } + + /** + * Verifies that the given {@link HttpMethod} is supported for the given {@link PersistentProperty}. + * + * @param httpMethod must not be {@literal null}. + * @param property must not be {@literal null}. + * @throws ResourceNotFoundException if the repository is not exported at all. + * @throws HttpRequestMethodNotSupportedException if the {@link PersistentProperty} does not support the given + * {@link HttpMethod}. Will contain all supported methods as indicators for clients. + */ + public void verifySupportedMethod(HttpMethod httpMethod, PersistentProperty property) + throws HttpRequestMethodNotSupportedException { + + Assert.notNull(httpMethod, "HTTP method must not be null!"); + Assert.notNull(property, "Resource type must not be null!"); + + if (!resourceMetadata.isExported()) { + throw new ResourceNotFoundException(); + } + + SupportedHttpMethods httpMethods = resourceMetadata.getSupportedHttpMethods(); + Collection supportedMethods = httpMethods.getMethodsFor(property); + + if (!supportedMethods.contains(httpMethod)) { + reject(httpMethod, supportedMethods); + } + } + + private static void reject(HttpMethod method, Collection supported) + throws HttpRequestMethodNotSupportedException { + + Set stringMethods = new HashSet(supported.size()); + + for (HttpMethod supportedMethod : supported) { + stringMethods.add(supportedMethod.name()); + } + + throw new HttpRequestMethodNotSupportedException(method.name(), stringMethods); + } } 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 index 2c35aeda2..b0b36aa16 100644 --- 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 @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 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. @@ -17,14 +17,14 @@ package org.springframework.data.rest.webmvc; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; +import static org.springframework.data.rest.core.mapping.ResourceType.*; +import static org.springframework.http.HttpMethod.*; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.data.rest.core.mapping.ResourceType; import org.springframework.data.rest.core.mapping.SupportedHttpMethods; 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; @@ -46,7 +46,7 @@ public class RootResourceInformationIntegrationTests extends AbstractControllerI public void getIsNotSupportedIfFindAllIsNotExported() { SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods(); - assertThat(supportedMethods.supports(HttpMethod.GET, ResourceType.COLLECTION), is(false)); + assertThat(supportedMethods.getMethodsFor(COLLECTION), not(hasItem(GET))); } /** @@ -56,6 +56,6 @@ public class RootResourceInformationIntegrationTests extends AbstractControllerI public void postIsNotSupportedIfSaveIsNotExported() { SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods(); - assertThat(supportedMethods.supports(HttpMethod.POST, ResourceType.COLLECTION), is(false)); + assertThat(supportedMethods.getMethodsFor(COLLECTION), not(hasItem(POST))); } } 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 eb78fdc00..5ee170b54 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 @@ -415,26 +415,6 @@ public class JpaWebTests extends CommonWebTests { assertJsonPathEquals("$.firstName", "Bilbo", overwrittenResponse); } - private List preparePersonResources(Person primary, Person... persons) throws Exception { - - Link peopleLink = client.discoverUnique("people"); - List links = new ArrayList(); - - MockHttpServletResponse primaryResponse = postAndGet(peopleLink, mapper.writeValueAsString(primary), - MediaType.APPLICATION_JSON); - links.add(client.assertHasLinkWithRel("siblings", primaryResponse)); - - for (Person person : persons) { - - String payload = mapper.writeValueAsString(person); - MockHttpServletResponse response = postAndGet(peopleLink, payload, MediaType.APPLICATION_JSON); - - links.add(client.assertHasLinkWithRel(Link.REL_SELF, response)); - } - - return links; - } - /** * @see DATAREST-217 */ @@ -631,6 +611,50 @@ public class JpaWebTests extends CommonWebTests { andExpect(status().isIAmATeapot()); } + /** + * @see DATAREST-523 + */ + @Test + public void augmentsCollectionAssociationUsingPost() throws Exception { + + List links = preparePersonResources(new Person("Frodo", "Baggins"), // + new Person("Bilbo", "Baggins")); + + Link frodosSiblingsLink = links.get(0).expand(); + Link bilboLink = links.get(1); + + for (int i = 1; i <= 2; i++) { + + mvc.perform(post(frodosSiblingsLink.getHref()).// + content(bilboLink.getHref()).// + contentType(TEXT_URI_LIST)).// + andExpect(status().isNoContent()); + + mvc.perform(get(frodosSiblingsLink.getHref())).// + andExpect(jsonPath("$._embedded.people", hasSize(i))); + } + } + + private List preparePersonResources(Person primary, Person... persons) throws Exception { + + Link peopleLink = client.discoverUnique("people"); + List links = new ArrayList(); + + MockHttpServletResponse primaryResponse = postAndGet(peopleLink, mapper.writeValueAsString(primary), + MediaType.APPLICATION_JSON); + links.add(client.assertHasLinkWithRel("siblings", primaryResponse)); + + for (Person person : persons) { + + String payload = mapper.writeValueAsString(person); + MockHttpServletResponse response = postAndGet(peopleLink, payload, MediaType.APPLICATION_JSON); + + links.add(client.assertHasLinkWithRel(Link.REL_SELF, response)); + } + + return links; + } + /** * Asserts the {@link Person} resource the given link points to contains siblings with the given names. *