DATAREST-409 - Port RepositoryInvoker API to Spring Data Commons.

Introduced SupportedHttpMethods abstraction to be able to test the exposure of HTTp methods based on a CrudMethods instance only. Moved ResourceType to the core module.
This commit is contained in:
Oliver Gierke
2014-11-12 19:57:04 +01:00
parent dbeec0a5de
commit 458cdfa7e8
28 changed files with 486 additions and 1330 deletions

View File

@@ -30,6 +30,7 @@ import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.event.AfterCreateEvent;
@@ -38,9 +39,10 @@ import org.springframework.data.rest.core.event.AfterSaveEvent;
import org.springframework.data.rest.core.event.BeforeCreateEvent;
import org.springframework.data.rest.core.event.BeforeDeleteEvent;
import org.springframework.data.rest.core.event.BeforeSaveEvent;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceType;
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
import org.springframework.data.rest.webmvc.support.BackendId;
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
import org.springframework.data.rest.webmvc.support.RepositoryEntityLinks;
@@ -113,7 +115,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
public ResponseEntity<?> optionsForCollectionResource(RootResourceInformation information) {
HttpHeaders headers = new HttpHeaders();
headers.setAllow(information.getSupportedMethods(ResourceType.COLLECTION));
SupportedHttpMethods supportedMethods = information.getSupportedMethods();
headers.setAllow(supportedMethods.getMethodsFor(ResourceType.COLLECTION));
return new ResponseEntity<Object>(headers, HttpStatus.OK);
}
@@ -243,7 +247,9 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
public ResponseEntity<?> optionsForItemResource(RootResourceInformation information) {
HttpHeaders headers = new HttpHeaders();
headers.setAllow(information.getSupportedMethods(ResourceType.ITEM));
SupportedHttpMethods supportedMethods = information.getSupportedMethods();
headers.setAllow(supportedMethods.getMethodsFor(ResourceType.ITEM));
headers.put("Accept-Patch", ACCEPT_PATCH_HEADERS);
return new ResponseEntity<Object>(headers, HttpStatus.OK);
@@ -449,12 +455,6 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
resourceInformation.verifySupportedMethod(HttpMethod.GET, ResourceType.ITEM);
RepositoryInvoker repoMethodInvoker = resourceInformation.getInvoker();
if (!repoMethodInvoker.exposesFindOne()) {
throw new ResourceNotFoundException();
}
return repoMethodInvoker.invokeFindOne(id);
return resourceInformation.getInvoker().invokeFindOne(id);
}
}

View File

@@ -36,14 +36,15 @@ import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.event.AfterLinkDeleteEvent;
import org.springframework.data.rest.core.event.AfterLinkSaveEvent;
import org.springframework.data.rest.core.event.BeforeLinkDeleteEvent;
import org.springframework.data.rest.core.event.BeforeLinkSaveEvent;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
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;
@@ -155,7 +156,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
final RepositoryInvoker repoMethodInvoker = repoRequest.getInvoker();
if (!repoMethodInvoker.exposesDelete()) {
// Can't delete a property if
if (repoRequest.getSupportedMethods().supports(HttpMethod.PUT, ResourceType.ITEM)) {
return new ResponseEntity<Resource<?>>(HttpStatus.METHOD_NOT_ALLOWED);
}
@@ -379,7 +381,8 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
final RepositoryInvoker invoker = repoRequest.getInvoker();
if (!invoker.exposesSave()) {
// 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());
}
@@ -443,7 +446,7 @@ class RepositoryPropertyReferenceController extends AbstractRepositoryRestContro
RepositoryInvoker invoker = repoRequest.getInvoker();
if (!invoker.exposesFindOne()) {
if (!repoRequest.getSupportedMethods().supports(method, ResourceType.ITEM)) {
throw new HttpRequestMethodNotSupportedException(method.name());
}

View File

@@ -25,7 +25,7 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.MethodResourceMapping;
import org.springframework.data.rest.core.mapping.ParameterMetadata;
import org.springframework.data.rest.core.mapping.ResourceMappings;

View File

@@ -1,26 +0,0 @@
/*
* 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;
}

View File

@@ -16,14 +16,15 @@
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.repository.invoker.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceType;
import org.springframework.data.rest.core.mapping.SearchResourceMappings;
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -43,6 +44,7 @@ public class RootResourceInformation {
public RootResourceInformation(ResourceMetadata metadata, PersistentEntity<?, ?> entity, RepositoryInvoker invoker) {
this.resourceMetadata = metadata;
if (resourceMetadata == null || !resourceMetadata.isExported()) {
this.invoker = null;
@@ -74,75 +76,8 @@ public class RootResourceInformation {
return persistentEntity;
}
/**
* Returns the supported {@link HttpMethod}s for the given {@link ResourceType}.
*
* @param resourcType must not be {@literal null}.
* @return
*/
public Set<HttpMethod> getSupportedMethods(ResourceType resourcType) {
Assert.notNull(resourcType, "Resource type must not be null!");
if (invoker == null) {
return Collections.emptySet();
}
Set<HttpMethod> methods = new HashSet<HttpMethod>();
methods.add(HttpMethod.OPTIONS);
switch (resourcType) {
case COLLECTION:
if (invoker.exposesFindAll()) {
methods.add(HttpMethod.GET);
methods.add(HttpMethod.HEAD);
}
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);
methods.add(HttpMethod.HEAD);
}
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);
public SupportedHttpMethods getSupportedMethods() {
return resourceMetadata.getSupportedHttpMethods();
}
/**
@@ -164,7 +99,8 @@ public class RootResourceInformation {
Assert.notNull(httpMethod, "HTTP method must not be null!");
Assert.notNull(resourceType, "Resource type must not be null!");
Collection<HttpMethod> supportedMethods = getSupportedMethods(resourceType);
SupportedHttpMethods httpMethods = resourceMetadata.getSupportedHttpMethods();
Collection<HttpMethod> supportedMethods = httpMethods.getMethodsFor(resourceType);
if (!supportedMethods.contains(httpMethod)) {

View File

@@ -45,8 +45,9 @@ import org.springframework.data.rest.core.mapping.ResourceDescription;
import org.springframework.data.rest.core.mapping.ResourceMapping;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.core.mapping.ResourceType;
import org.springframework.data.rest.core.mapping.SimpleResourceDescription;
import org.springframework.data.rest.webmvc.ResourceType;
import org.springframework.data.rest.core.mapping.SupportedHttpMethods;
import org.springframework.data.rest.webmvc.RootResourceInformation;
import org.springframework.data.rest.webmvc.json.JacksonMetadata;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
@@ -121,14 +122,16 @@ public class RootResourceInformationToAlpsDescriptorConverter {
descriptors.add(representationDescriptor);
for (HttpMethod method : resourceInformation.getSupportedMethods(ResourceType.COLLECTION)) {
SupportedHttpMethods supportedHttpMethods = resourceInformation.getSupportedMethods();
for (HttpMethod method : supportedHttpMethods.getMethodsFor(ResourceType.COLLECTION)) {
if (!UNDOCUMENTED_METHODS.contains(method)) {
descriptors.add(buildCollectionResourceDescriptor(type, resourceInformation, representationDescriptor, method));
}
}
for (HttpMethod method : resourceInformation.getSupportedMethods(ResourceType.ITEM)) {
for (HttpMethod method : supportedHttpMethods.getMethodsFor(ResourceType.ITEM)) {
if (!UNDOCUMENTED_METHODS.contains(method)) {
descriptors.add(buildItemResourceDescriptor(resourceInformation, representationDescriptor, method));

View File

@@ -22,7 +22,7 @@ import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.MethodParameter;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.rest.webmvc.IncomingRequest;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.ResourceNotFoundException;

View File

@@ -48,6 +48,8 @@ import org.springframework.data.geo.GeoModule;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.invoker.DefaultRepositoryInvokerFactory;
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.UriToEntityConverter;
@@ -57,8 +59,6 @@ import org.springframework.data.rest.core.config.ProjectionDefinitionConfigurati
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.event.AnnotatedHandlerBeanPostProcessor;
import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener;
import org.springframework.data.rest.core.invoke.DefaultRepositoryInvokerFactory;
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceDescription;
import org.springframework.data.rest.core.mapping.ResourceMappings;
@@ -66,8 +66,8 @@ import org.springframework.data.rest.core.projection.ProxyProjectionFactory;
import org.springframework.data.rest.core.support.DomainObjectMerger;
import org.springframework.data.rest.core.support.RepositoryRelProvider;
import org.springframework.data.rest.core.util.UUIDConverter;
import org.springframework.data.rest.webmvc.BaseUriAwareController;
import org.springframework.data.rest.webmvc.BaseUri;
import org.springframework.data.rest.webmvc.BaseUriAwareController;
import org.springframework.data.rest.webmvc.BaseUriAwareHandlerMapping;
import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;

View File

@@ -17,9 +17,9 @@ package org.springframework.data.rest.webmvc.config;
import org.springframework.core.MethodParameter;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.invoker.RepositoryInvoker;
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.invoke.RepositoryInvoker;
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.RootResourceInformation;
import org.springframework.util.Assert;

View File

@@ -21,9 +21,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.invoker.RepositoryInvokerFactory;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.invoke.RepositoryInvokerFactory;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;

View File

@@ -20,6 +20,8 @@ import static org.junit.Assert.*;
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;
@@ -43,8 +45,8 @@ public class RootResourceInformationIntegrationTests extends AbstractControllerI
@Test
public void getIsNotSupportedIfFindAllIsNotExported() {
RootResourceInformation information = getResourceInformation(Address.class);
assertThat(information.supports(HttpMethod.GET, ResourceType.COLLECTION), is(false));
SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods();
assertThat(supportedMethods.supports(HttpMethod.GET, ResourceType.COLLECTION), is(false));
}
/**
@@ -53,7 +55,7 @@ public class RootResourceInformationIntegrationTests extends AbstractControllerI
@Test
public void postIsNotSupportedIfSaveIsNotExported() {
RootResourceInformation information = getResourceInformation(Address.class);
assertThat(information.supports(HttpMethod.POST, ResourceType.COLLECTION), is(false));
SupportedHttpMethods supportedMethods = getResourceInformation(Address.class).getSupportedMethods();
assertThat(supportedMethods.supports(HttpMethod.POST, ResourceType.COLLECTION), is(false));
}
}

View File

@@ -15,13 +15,10 @@
*/
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.data.rest.core.mapping.ResourceType.*;
import static org.springframework.http.HttpMethod.*;
import org.atteo.evo.inflector.English;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -31,7 +28,7 @@ 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.repository.invoker.RepositoryInvoker;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
import org.springframework.web.HttpRequestMethodNotSupportedException;
@@ -57,90 +54,6 @@ public class RootResourceInformationUnitTests {
this.information = new RootResourceInformation(metadata, entity, invoker);
}
/**
* @see DATAREST-217, DATAREST-330
*/
@Test
public void defaultsSupportedHttpMethodsForItemResource() {
assertThat(information.getSupportedMethods(ResourceType.ITEM), hasItems(GET, PUT, PATCH, DELETE, OPTIONS));
assertThat(information.getSupportedMethods(ResourceType.ITEM), not(hasItems(POST)));
assertThat(information.getSupportedMethods(COLLECTION), hasItems(GET, POST, OPTIONS));
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));
}
/**
* @see DATAREST-330
*/
@Test
public void supportsHeadIfFindAllIsExposed() {
when(invoker.exposesFindAll()).thenReturn(true);
assertThat(information.supports(HEAD, COLLECTION), is(true));
}
/**
* @see DATAREST-330
*/
@Test
public void doesNotSupportHeadIfFindAllIsNotExposed() {
when(invoker.exposesFindAll()).thenReturn(false);
assertThat(information.supports(HEAD, COLLECTION), is(false));
}
/**
* @see DATAREST-330
*/
@Test
public void supportsHeadIfFindOneIsExposed() {
when(invoker.exposesFindOne()).thenReturn(true);
assertThat(information.supports(HEAD, ITEM), is(true));
}
/**
* @see DATAREST-330
*/
@Test
public void doesNotSupportHeadIfFindOneIsNotExposed() {
when(invoker.exposesFindOne()).thenReturn(false);
assertThat(information.supports(HEAD, ITEM), is(false));
}
/**
* @see DATAREST-330
*/