From 458cdfa7e88c25bc43cea09522d5c230f30a279d Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Wed, 12 Nov 2014 19:57:04 +0100 Subject: [PATCH] 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. --- .../data/rest/core/invoke/CrudMethod.java | 71 ----- .../core/invoke/CrudRepositoryInvoker.java | 126 -------- .../DefaultRepositoryInvokerFactory.java | 102 ------ .../PagingAndSortingRepositoryInvoker.java | 68 ---- .../invoke/ReflectionRepositoryInvoker.java | 296 ------------------ .../RepositoryInvocationInformation.java | 80 ----- .../rest/core/invoke/RepositoryInvoker.java | 41 --- .../core/invoke/RepositoryInvokerFactory.java | 33 -- .../CrudMethodsSupportedHttpMethods.java | 209 +++++++++++++ .../core/mapping/MappingResourceMetadata.java | 10 + .../RepositoryAwareResourceInformation.java | 11 + .../rest/core/mapping/ResourceMetadata.java | 13 +- .../data/rest/core/mapping}/ResourceType.java | 2 +- .../core/mapping/SupportedHttpMethods.java | 74 +++++ ...CrudRepositoryInvokerIntegrationTests.java | 133 -------- ...tionRepositoryInvokerIntegrationTests.java | 187 ----------- ...dMethodsSupportedHttpMethodsUnitTests.java | 123 ++++++++ .../webmvc/RepositoryEntityController.java | 20 +- ...RepositoryPropertyReferenceController.java | 11 +- .../webmvc/RepositorySearchController.java | 2 +- .../rest/webmvc/RootResourceInformation.java | 80 +---- ...eInformationToAlpsDescriptorConverter.java | 9 +- ...ResourceHandlerMethodArgumentResolver.java | 2 +- .../RepositoryRestMvcConfiguration.java | 6 +- ...ormationHandlerMethodArgumentResolver.java | 4 +- .../AbstractControllerIntegrationTests.java | 2 +- ...otResourceInformationIntegrationTests.java | 10 +- .../RootResourceInformationUnitTests.java | 91 +----- 28 files changed, 486 insertions(+), 1330 deletions(-) delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudMethod.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvoker.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/DefaultRepositoryInvokerFactory.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/PagingAndSortingRepositoryInvoker.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvoker.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvocationInformation.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvoker.java delete mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvokerFactory.java create mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java rename {spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc => spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping}/ResourceType.java (93%) create mode 100644 spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SupportedHttpMethods.java delete mode 100644 spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvokerIntegrationTests.java delete mode 100644 spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvokerIntegrationTests.java create mode 100644 spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudMethod.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudMethod.java deleted file mode 100644 index 445494f29..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudMethod.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.springframework.data.rest.core.invoke; - -import java.lang.reflect.Method; - -/** - * Represents one of the CRUD methods supported by - * {@link org.springframework.data.repository.PagingAndSortingRepository} or - * {@link org.springframework.data.repository.CrudRepository}. - * - * @author Jon Brisbin - */ -public enum CrudMethod { - - COUNT, DELETE_ALL, DELETE_ONE, DELETE_SOME, FIND_ALL, FIND_ONE, FIND_SOME, SAVE_ONE, SAVE_SOME; - - /** - * Get an enum from a {@link Method}. Narrow down overridden methods by looking for {@link Iterable} in the first - * parameter, which tells us it is a '_SOME' type. - * - * @param m The CRUD method from the repository interface. - * @return An enum representing which CRUD operation this method represents. - */ - public static CrudMethod fromMethod(Method m) { - String s = m.getName(); - Class[] paramTypes = m.getParameterTypes(); - boolean some = (paramTypes.length > 0 && Iterable.class.isAssignableFrom(paramTypes[0])); - if ("count".equals(s)) { - return COUNT; - } else if ("delete".equals(s)) { - return (some ? DELETE_SOME : DELETE_ONE); - } else if ("deleteAll".equals(s)) { - return DELETE_ALL; - } else if ("findAll".equals(s)) { - return (some ? FIND_SOME : FIND_ALL); - } else if ("findOne".equals(s)) { - return FIND_ONE; - } else if ("save".equals(s)) { - return (some ? SAVE_SOME : SAVE_ONE); - } else { - return null; - } - } - - /** - * Turn this enum into a method name. - * - * @return The method name as a string. - */ - public String toMethodName() { - switch (this) { - case COUNT: - return "count"; - case DELETE_ALL: - return "deleteAll"; - case DELETE_ONE: - case DELETE_SOME: - return "delete"; - case FIND_ALL: - case FIND_SOME: - return "findAll"; - case FIND_ONE: - return "findOne"; - case SAVE_ONE: - case SAVE_SOME: - return "save"; - default: - return null; - } - } - -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvoker.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvoker.java deleted file mode 100644 index 82fb74c05..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvoker.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * 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. - * 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.core.invoke; - -import java.io.Serializable; -import java.lang.reflect.Method; - -import org.springframework.core.convert.ConversionService; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.core.CrudMethods; -import org.springframework.data.repository.core.RepositoryInformation; - -/** - * {@link RepositoryInvoker} to shortcut execution of CRUD methods into direct calls on a {@link CrudRepository}. Used - * to avoid reflection overhead introduced by the base class if we know we work with a {@link CrudRepository}. - * - * @author Oliver Gierke - */ -class CrudRepositoryInvoker extends ReflectionRepositoryInvoker { - - private final CrudRepository repository; - private final CrudMethods crudMethods; - - private final boolean customSaveMethod; - private final boolean customFindOneMethod; - private final boolean customDeleteMethod; - - /** - * Creates a new {@link CrudRepositoryInvoker} for the given {@link CrudRepository}, {@link RepositoryInformation} and - * {@link ConversionService}. - * - * @param repository must not be {@literal null}. - * @param information must not be {@literal null}. - * @param conversionService must not be {@literal null}. - */ - public CrudRepositoryInvoker(CrudRepository repository, RepositoryInformation information, - ConversionService conversionService) { - - super(repository, information, conversionService); - this.repository = repository; - this.crudMethods = information.getCrudMethods(); - - this.customSaveMethod = isRedeclaredMethod(crudMethods.getSaveMethod()); - this.customFindOneMethod = isRedeclaredMethod(crudMethods.getFindOneMethod()); - this.customDeleteMethod = isRedeclaredMethod(crudMethods.getDeleteMethod()); - } - - /** - * Invokes the method equivalent to {@link CrudRepository#findAll()}. - * - * @return - */ - protected Iterable invokeFindAll() { - return repository.findAll(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort) - */ - @Override - public Iterable invokeFindAll(Sort pageable) { - return repository.findAll(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable) - */ - @Override - public Iterable invokeFindAll(Pageable pageable) { - return repository.findAll(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable) - */ - @Override - @SuppressWarnings("unchecked") - public T invokeFindOne(Serializable id) { - return customFindOneMethod ? super. invokeFindOne(id) : (T) repository.findOne(convertId(id)); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.ReflectionRepositoryInvoker#invokeSave(java.lang.Object) - */ - @Override - public T invokeSave(T entity) { - return customSaveMethod ? super.invokeSave(entity) : repository.save(entity); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable) - */ - @Override - public void invokeDelete(Serializable id) { - - if (customDeleteMethod) { - super.invokeDelete(id); - } else { - repository.delete(convertId(id)); - } - } - - private boolean isRedeclaredMethod(Method method) { - return !method.getDeclaringClass().equals(CrudRepository.class); - } -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/DefaultRepositoryInvokerFactory.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/DefaultRepositoryInvokerFactory.java deleted file mode 100644 index 58f2e40eb..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/DefaultRepositoryInvokerFactory.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 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.core.invoke; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -import org.springframework.core.convert.ConversionService; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.data.repository.core.RepositoryInformation; -import org.springframework.data.repository.support.Repositories; -import org.springframework.util.Assert; - -/** - * Default implementation of {@link RepositoryInvokerFactory} to inspect the requested repository type and create a - * matching {@link RepositoryInvoker} that suits the repository best. That means, the more concrete the base interface - * of the repository is, the more concrete will the actual invoker become - which means it will favor concrete method - * invocations over reflection ones. - * - * @author Oliver Gierke - */ -public class DefaultRepositoryInvokerFactory implements RepositoryInvokerFactory { - - private final Repositories repositories; - private final ConversionService conversionService; - private final Map, RepositoryInvoker> invokers; - - /** - * Creates a new {@link DefaultRepositoryInvokerFactory} for the given {@link Repositories} and - * {@link ConversionService}. - * - * @param repositories must not be {@literal null}. - * @param conversionService must not be {@literal null}. - */ - public DefaultRepositoryInvokerFactory(Repositories repositories, ConversionService conversionService) { - - Assert.notNull(repositories, "Repositories must not be null!"); - Assert.notNull(conversionService, "ConversionService must not be null!"); - - this.repositories = repositories; - this.conversionService = conversionService; - this.invokers = new HashMap, RepositoryInvoker>(); - - } - - /** - * Creates a {@link RepositoryInvoker} for the repository managing the given domain type. - * - * @param domainType - * @return - */ - @SuppressWarnings("unchecked") - private RepositoryInvoker prepareInvokers(Class domainType) { - - Object repository = repositories.getRepositoryFor(domainType); - RepositoryInformation information = repositories.getRepositoryInformationFor(domainType); - - if (repository instanceof PagingAndSortingRepository) { - return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository) repository, - information, conversionService); - } else if (repository instanceof CrudRepository) { - return new CrudRepositoryInvoker((CrudRepository) repository, information, - conversionService); - } else { - return new ReflectionRepositoryInvoker(repository, information, conversionService); - } - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvokerFactory#getInvokerFor(java.lang.Class) - */ - @Override - public RepositoryInvoker getInvokerFor(Class domainType) { - - RepositoryInvoker invoker = invokers.get(domainType); - - if (invoker != null) { - return invoker; - } - - invoker = prepareInvokers(domainType); - invokers.put(domainType, invoker); - - return invoker; - } -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/PagingAndSortingRepositoryInvoker.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/PagingAndSortingRepositoryInvoker.java deleted file mode 100644 index cf5665e72..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/PagingAndSortingRepositoryInvoker.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 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.core.invoke; - -import java.io.Serializable; - -import org.springframework.core.convert.ConversionService; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.PagingAndSortingRepository; -import org.springframework.data.repository.core.RepositoryInformation; - -/** - * A special {@link RepositoryInvoker} that shortcuts invocations to methods on {@link PagingAndSortingRepository} to - * avoid reflection overhead introduced by the superclass. - * - * @author Oliver Gierke - */ -class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker { - - private final PagingAndSortingRepository repository; - - /** - * Creates a new {@link PagingAndSortingRepositoryInvoker} using the given repository, {@link RepositoryInformation} - * and {@link ConversionService}. - * - * @param repository must not be {@literal null}. - * @param information must not be {@literal null}. - * @param conversionService must not be {@literal null}. - */ - public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository repository, - RepositoryInformation information, ConversionService conversionService) { - - super(repository, information, conversionService); - this.repository = repository; - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort) - */ - @Override - public Iterable invokeFindAll(Sort sort) { - return sort == null ? invokeFindAll() : repository.findAll(sort); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.CrudRepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable) - */ - @Override - public Iterable invokeFindAll(Pageable pageable) { - return pageable == null ? invokeFindAll() : repository.findAll(pageable); - } -} 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 deleted file mode 100644 index a07ac8828..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvoker.java +++ /dev/null @@ -1,296 +0,0 @@ -/* - * 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. - * 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.core.invoke; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.springframework.core.MethodParameter; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.convert.ConversionService; -import org.springframework.core.convert.TypeDescriptor; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; -import org.springframework.data.repository.core.CrudMethods; -import org.springframework.data.repository.core.RepositoryInformation; -import org.springframework.data.repository.query.Param; -import org.springframework.data.rest.core.annotation.RestResource; -import org.springframework.hateoas.core.AnnotationAttribute; -import org.springframework.hateoas.core.MethodParameters; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.util.StringUtils; - -/** - * Base {@link RepositoryInvoker} using reflection to invoke methods on Spring Data Repositories. - * - * @author Oliver Gierke - */ -class ReflectionRepositoryInvoker implements RepositoryInvoker { - - private static final AnnotationAttribute PARAM_ANNOTATION = new AnnotationAttribute(Param.class); - - private final Object repository; - private final CrudMethods methods; - private final RepositoryInformation information; - private final ConversionService conversionService; - - /** - * Creates a new {@link ReflectionRepositoryInvoker} for the given repository, {@link RepositoryInformation} and - * {@link ConversionService}. - * - * @param repository must not be {@literal null}. - * @param information must not be {@literal null}. - * @param conversionService must not be {@literal null}. - */ - public ReflectionRepositoryInvoker(Object repository, RepositoryInformation information, - ConversionService conversionService) { - - Assert.notNull(repository, "Repository must not be null!"); - Assert.notNull(information, "RepositoryInformation must not be null!"); - Assert.notNull(conversionService, "ConversionService must not be null!"); - - this.repository = repository; - this.methods = information.getCrudMethods(); - this.information = information; - 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() - */ - @Override - public boolean exposesFindAll() { - return methods.hasFindAllMethod() && exposes(methods.getFindAllMethod()); - } - - /* (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Sort) - */ - @Override - @SuppressWarnings("unchecked") - public Iterable invokeFindAll(Sort sort) { - return (Iterable) invoke(methods.getFindAllMethod(), sort); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindAll(org.springframework.data.domain.Pageable) - */ - @Override - public Iterable invokeFindAll(Pageable pageable) { - - if (!exposesFindAll()) { - return Collections.emptyList(); - } - - Method method = methods.getFindAllMethod(); - Class[] types = method.getParameterTypes(); - - if (types.length == 0) { - return invoke(method); - } - - if (Sort.class.isAssignableFrom(types[0])) { - return invoke(method, pageable == null ? null : pageable.getSort()); - } - - 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() - */ - @Override - public boolean exposesSave() { - return methods.hasSaveMethod() && exposes(methods.getSaveMethod()); - } - - /* (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeSave(java.lang.Object) - */ - @Override - public T invokeSave(T object) { - 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() - */ - @Override - public boolean exposesFindOne() { - return methods.hasFindOneMethod() && exposes(methods.getFindOneMethod()); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeFindOne(java.io.Serializable) - */ - @Override - public T invokeFindOne(Serializable id) { - 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() - */ - @Override - public boolean exposesDelete() { - return methods.hasDelete() && exposes(methods.getDeleteMethod()); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeDelete(java.io.Serializable) - */ - @Override - @SuppressWarnings("unchecked") - public void invokeDelete(Serializable id) { - - Method method = methods.getDeleteMethod(); - Class parameterType = method.getParameterTypes()[0]; - List> idTypes = Arrays.asList(information.getIdType(), Serializable.class); - - if (idTypes.contains(parameterType)) { - invoke(method, convertId(id)); - } else { - invoke(method, invokeFindOne(id)); - } - } - - private boolean exposes(Method method) { - - RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class); - return annotation == null ? true : annotation.exported(); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.invoke.RepositoryInvoker#invokeQueryMethod(java.lang.reflect.Method, java.util.Map, org.springframework.data.domain.Pageable, org.springframework.data.domain.Sort) - */ - @Override - public Object invokeQueryMethod(Method method, Map parameters, Pageable pageable, Sort sort) { - return invoke(method, prepareParameters(method, parameters, pageable, sort)); - } - - private Object[] prepareParameters(Method method, Map rawParameters, Pageable pageable, Sort sort) { - - List parameters = new MethodParameters(method, PARAM_ANNOTATION).getParameters(); - - if (parameters.isEmpty()) { - return new Object[0]; - } - - Object[] result = new Object[parameters.size()]; - Sort sortToUse = pageable == null ? sort : pageable.getSort(); - - for (int i = 0; i < result.length; i++) { - - MethodParameter param = parameters.get(i); - Class targetType = param.getParameterType(); - - if (Pageable.class.isAssignableFrom(targetType)) { - result[i] = pageable; - } else if (Sort.class.isAssignableFrom(targetType)) { - result[i] = sortToUse; - } else { - - String parameterName = param.getParameterName(); - - if (!StringUtils.hasText(parameterName)) { - throw new IllegalArgumentException("No @Param annotation found on query method " + method.getName() - + " for parameter " + parameterName); - } - - String[] parameterValue = rawParameters.get(parameterName); - Object value = parameterValue == null ? null : parameterValue.length == 1 ? parameterValue[0] : parameterValue; - - result[i] = conversionService.convert(value, TypeDescriptor.forObject(value), new TypeDescriptor(param)); - } - } - - return result; - } - - /** - * Invokes the given method with the given arguments on the backing repository. - * - * @param method - * @param arguments - * @return - */ - @SuppressWarnings("unchecked") - private T invoke(Method method, Object... arguments) { - - ReflectionUtils.makeAccessible(method); - return (T) ReflectionUtils.invokeMethod(method, repository, arguments); - } - - /** - * Converts the given id into the id type of the backing repository. - * - * @param id must not be {@literal null}. - * @return - */ - protected Serializable convertId(Serializable id) { - Assert.notNull(id, "Id must not be null!"); - return conversionService.convert(id, information.getIdType()); - } -} 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 deleted file mode 100644 index 637986666..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvocationInformation.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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. - * 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.core.invoke; - -/** - * Meta-information about the methods a repository exposes. - * - * @author Oliver Gierke - */ -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-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvoker.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvoker.java deleted file mode 100644 index 70ffa05d9..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvoker.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 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.core.invoke; - -import java.io.Serializable; -import java.lang.reflect.Method; -import java.util.Map; - -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.Sort; - -/** - * @author Oliver Gierke - */ -public interface RepositoryInvoker extends RepositoryInvocationInformation { - - T invokeSave(T object); - - T invokeFindOne(Serializable id); - - Iterable invokeFindAll(Pageable pageable); - - Iterable invokeFindAll(Sort sort); - - void invokeDelete(Serializable serializable); - - Object invokeQueryMethod(Method method, Map parameters, Pageable pageable, Sort sort); -} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvokerFactory.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvokerFactory.java deleted file mode 100644 index fddd280fe..000000000 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/invoke/RepositoryInvokerFactory.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 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.core.invoke; - -/** - * Interface for a factory to create {@link RepositoryInvoker} instances for repositories managing a particular domain - * type. - * - * @author Oliver Gierke - */ -public interface RepositoryInvokerFactory { - - /** - * Returns the {@link RepositoryInvoker} for a repository managing the given domain type. - * - * @param domainType must not be {@literal null}. - * @return - */ - RepositoryInvoker getInvokerFor(Class domainType); -} 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 new file mode 100644 index 000000000..0417c260c --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java @@ -0,0 +1,209 @@ +/* + * 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.core.mapping; + +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.repository.core.CrudMethods; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; + +/** + * {@link SupportedHttpMethods} that are determined by a {@link CrudMethods} instance. + * + * @author Oliver Gierke + * @since 2.3 + */ +public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods { + + private final ExposureAwareCrudMethods exposedMethods; + + /** + * Creates a new {@link CrudMethodsSupportedHttpMethods} for the given {@link CrudMethods}. + * + * @param crudMethods must not be {@literal null}. + */ + public CrudMethodsSupportedHttpMethods(CrudMethods crudMethods) { + + Assert.notNull(crudMethods, "CrudMethods must not be null!"); + + 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) + */ + @Override + public Set getMethodsFor(ResourceType resourcType) { + + Assert.notNull(resourcType, "Resource type must not be null!"); + + Set methods = new HashSet(); + methods.add(HttpMethod.OPTIONS); + + switch (resourcType) { + case COLLECTION: + + if (exposedMethods.exposesFindAll()) { + methods.add(HttpMethod.GET); + methods.add(HttpMethod.HEAD); + } + + if (exposedMethods.exposesSave()) { + methods.add(HttpMethod.POST); + } + + break; + + case ITEM: + + if (exposedMethods.exposesDelete() && exposedMethods.exposesFindOne()) { + methods.add(HttpMethod.DELETE); + } + + if (exposedMethods.exposesFindOne()) { + methods.add(HttpMethod.GET); + methods.add(HttpMethod.HEAD); + } + + if (exposedMethods.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); + } + + /** + * @author Oliver Gierke + */ + private static class DefaultExposureAwareCrudMethods implements ExposureAwareCrudMethods { + + private final CrudMethods crudMethods; + + /** + * @param exposedMethods + */ + public DefaultExposureAwareCrudMethods(CrudMethods crudMethods) { + this.crudMethods = crudMethods; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesSave() + */ + @Override + public boolean exposesSave() { + return exposes(crudMethods.getSaveMethod()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesDelete() + */ + @Override + public boolean exposesDelete() { + return exposes(crudMethods.getDeleteMethod()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesFindOne() + */ + @Override + public boolean exposesFindOne() { + return exposes(crudMethods.getFindOneMethod()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesFindAll() + */ + @Override + public boolean exposesFindAll() { + return exposes(crudMethods.getFindAllMethod()); + } + + private static boolean exposes(Method method) { + + if (method == null) { + return false; + } + + RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class); + return annotation == null ? true : annotation.exported(); + } + } + + /** + * @author Oliver Gierke + */ + interface ExposureAwareCrudMethods { + + /** + * Returns whether the repository exposes the save method. + * + * @return + */ + boolean exposesSave(); + + /** + * Returns whether the repository exposes the delete method. + * + * @return + */ + boolean exposesDelete(); + + /** + * Returns whether the repository exposes the method to find a single object. + * + * @return + */ + boolean exposesFindOne(); + + /** + * Returns whether the repository exposes the method to find all objects. + * + * @return + */ + boolean exposesFindAll(); + } +} diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MappingResourceMetadata.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MappingResourceMetadata.java index 632eb145b..b52455b04 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MappingResourceMetadata.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/MappingResourceMetadata.java @@ -21,6 +21,7 @@ import java.util.Map; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.rest.core.mapping.SupportedHttpMethods.NoSupportedMethods; /** * {@link ResourceMetadata} based on a {@link PersistentEntity}. @@ -100,4 +101,13 @@ public class MappingResourceMetadata extends TypeBasedCollectionResourceMapping public SearchResourceMappings getSearchResourceMappings() { return new SearchResourceMappings(Collections. emptyList()); } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSupportedHttpMethods() + */ + @Override + public SupportedHttpMethods getSupportedHttpMethods() { + return NoSupportedMethods.INSTANCE; + } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java index eb67dea6a..559d21c45 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceInformation.java @@ -34,6 +34,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata { private final CollectionResourceMapping mapping; private final RepositoryResourceMappings provider; private final RepositoryMetadata repositoryInterface; + private final SupportedHttpMethods crudMethodsSupportedHttpMethods; /** * Creates a new {@link RepositoryAwareResourceInformation} for the given {@link Repositories}, @@ -56,6 +57,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata { this.mapping = mapping; this.provider = provider; this.repositoryInterface = repositoryMetadata; + this.crudMethodsSupportedHttpMethods = new CrudMethodsSupportedHttpMethods(repositoryMetadata.getCrudMethods()); } /** @@ -186,4 +188,13 @@ class RepositoryAwareResourceInformation implements ResourceMetadata { public SearchResourceMappings getSearchResourceMappings() { return provider.getSearchResourceMappings(repositoryInterface.getRepositoryInterface()); } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.ResourceMetadata#getSupportedHttpMethods() + */ + @Override + public SupportedHttpMethods getSupportedHttpMethods() { + return crudMethodsSupportedHttpMethods; + } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMetadata.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMetadata.java index 125f1c202..0b94565e1 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMetadata.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceMetadata.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. @@ -16,9 +16,10 @@ package org.springframework.data.rest.core.mapping; import org.springframework.data.mapping.PersistentProperty; +import org.springframework.http.HttpMethod; /** - * Interface for metadata of resources exposed throught the system. + * Interface for metadata of resources exposed through the system. * * @author Oliver Gierke */ @@ -62,4 +63,12 @@ public interface ResourceMetadata extends CollectionResourceMapping { * @return */ SearchResourceMappings getSearchResourceMappings(); + + /** + * Returns the supported {@link HttpMethod}s for the given {@link ResourceType}. + * + * @param resourcType must not be {@literal null}. + * @return + */ + SupportedHttpMethods getSupportedHttpMethods(); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceType.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceType.java similarity index 93% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceType.java rename to spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceType.java index 72c51b13b..63e7f3809 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceType.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/ResourceType.java @@ -13,7 +13,7 @@ * 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.core.mapping; /** * An enum listing all supported resource types. 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 new file mode 100644 index 000000000..69503609d --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/SupportedHttpMethods.java @@ -0,0 +1,74 @@ +/* + * 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.core.mapping; + +import java.util.Collections; +import java.util.Set; + +import org.springframework.http.HttpMethod; + +/** + * An API to discover the {@link HttpMethod}s supported on a given {@link ResourceType}. + * + * @author Oliver Gierke + */ +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}. + * @return + */ + Set getMethodsFor(ResourceType resourcType); + + /** + * Null object to abstract the absence of any support for any HTTP method. + * + * @author Oliver Gierke + */ + enum NoSupportedMethods implements SupportedHttpMethods { + + INSTANCE; + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.core.mapping.SupportedHttpMethods#getSupportedHttpMethods(org.springframework.data.rest.core.mapping.ResourceType) + */ + @Override + public Set getMethodsFor(ResourceType resourcType) { + return Collections.emptySet(); + } + + /* + * (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) { + return false; + } + } +} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvokerIntegrationTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvokerIntegrationTests.java deleted file mode 100644 index bc9ea5f73..000000000 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/CrudRepositoryInvokerIntegrationTests.java +++ /dev/null @@ -1,133 +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.core.invoke; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; - -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.junit.Test; -import org.springframework.aop.framework.ProxyFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.core.convert.ConversionService; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.support.Repositories; -import org.springframework.data.rest.core.AbstractIntegrationTests; -import org.springframework.data.rest.core.domain.jpa.Order; -import org.springframework.data.rest.core.domain.jpa.OrderRepository; -import org.springframework.data.rest.core.domain.jpa.Person; -import org.springframework.data.rest.core.domain.jpa.PersonRepository; -import org.springframework.format.support.DefaultFormattingConversionService; - -/** - * Intgration tests for {@link CrudRepositoryInvoker}. - * - * @author Oliver Gierke - */ -public class CrudRepositoryInvokerIntegrationTests extends AbstractIntegrationTests { - - @Autowired ApplicationContext context; - @Autowired PersonRepository personRepository; - @Autowired OrderRepository orderRepository; - - /** - * @see DATAREST-216 - */ - @Test - public void invokesRedeclaredSave() { - - RepositoryInvoker invoker = getInvokerFor(orderRepository, OrderRepository.class); - - Person person = personRepository.findOne(1L); - invoker.invokeSave(new Order(person)); - } - - /** - * @see DATAREST-216 - */ - @Test - public void invokesRedeclaredFindOne() { - - Person person = personRepository.findOne(1L); - Order order = orderRepository.save(new Order(person)); - - RepositoryInvoker invoker = getInvokerFor(orderRepository, OrderRepository.class); - invoker.invokeFindOne(order.getId()); - } - - /** - * @see DATAREST-216 - */ - @Test - public void invokesDeleteOnCrudRepository() { - - Person person = personRepository.findOne(1L); - Order order = orderRepository.save(new Order(person)); - - RepositoryInvoker invoker = getInvokerFor(orderRepository, CrudRepository.class); - invoker.invokeDelete(order.getId()); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - private RepositoryInvoker getInvokerFor(Object repository, Class expectedType) { - - Object proxy = getVerifyingProxy(repository, expectedType); - Repositories repositories = new Repositories(context); - ConversionService conversionService = new DefaultFormattingConversionService(); - - return new CrudRepositoryInvoker((CrudRepository) proxy, repositories.getRepositoryInformationFor(Order.class), - conversionService); - } - - @SuppressWarnings("unchecked") - private static T getVerifyingProxy(T target, Class expectedType) { - - ProxyFactory factory = new ProxyFactory(); - factory.setInterfaces(target.getClass().getInterfaces()); - factory.setTarget(target); - factory.addAdvice(new VerifyingMethodInterceptor(expectedType)); - - return (T) factory.getProxy(); - } - - /** - * {@link MethodInterceptor} to verifiy the invocation was triggered on the given type. - * - * @author Oliver Gierke - */ - @SuppressWarnings("rawtypes") - private static final class VerifyingMethodInterceptor implements MethodInterceptor { - - private final Class expectedInvocationTarget; - - public VerifyingMethodInterceptor(Class expectedInvocationTarget) { - this.expectedInvocationTarget = expectedInvocationTarget; - } - - @Override - public Object invoke(MethodInvocation invocation) throws Throwable { - - Class type = invocation.getMethod().getDeclaringClass(); - - assertThat("Expected method invocation on " + expectedInvocationTarget + " but was invoked on " + type + "!", - type, is(equalTo(expectedInvocationTarget))); - - return invocation.proceed(); - } - } -} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvokerIntegrationTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvokerIntegrationTests.java deleted file mode 100644 index a40de24f3..000000000 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/invoke/ReflectionRepositoryInvokerIntegrationTests.java +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 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.core.invoke; - -import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; - -import java.lang.reflect.Method; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.persistence.EntityManager; - -import org.bson.types.ObjectId; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Matchers; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.core.convert.ConversionService; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.mongodb.MongoDbFactory; -import org.springframework.data.mongodb.core.MongoTemplate; -import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean; -import org.springframework.data.repository.CrudRepository; -import org.springframework.data.repository.core.RepositoryInformation; -import org.springframework.data.repository.support.Repositories; -import org.springframework.data.rest.core.AbstractIntegrationTests; -import org.springframework.data.rest.core.domain.jpa.Author; -import org.springframework.data.rest.core.domain.jpa.Order; -import org.springframework.data.rest.core.domain.jpa.OrderRepository; -import org.springframework.data.rest.core.domain.jpa.Person; -import org.springframework.data.rest.core.domain.jpa.PersonRepository; - -/** - * Integration tests for {@link ReflectionRepositoryInvoker}. - * - * @author Oliver Gierke - */ -public class ReflectionRepositoryInvokerIntegrationTests extends AbstractIntegrationTests { - - @Autowired Repositories repositories; - @Autowired ConversionService conversionService; - @Autowired PersonRepository repository; - @Autowired OrderRepository orderRepository; - @Autowired EntityManager em; - - RepositoryInformation information; - RepositoryInvoker invoker; - - @Before - public void setUp() { - - information = repositories.getRepositoryInformationFor(Person.class); - invoker = new ReflectionRepositoryInvoker(repository, information, conversionService); - } - - @Test - public void invokesFindOneWithStringIdCorrectly() { - - Person person = repository.findAll().iterator().next(); - assertThat(person, is(notNullValue())); - - Object result = invoker.invokeFindOne(person.getId().toString()); - assertThat(result, is(instanceOf(Person.class))); - } - - @Test - public void invokesFindAllWithoutPageableCorrectly() { - - Iterable result = invoker.invokeFindAll((Pageable) null); - assertThat(result, is(instanceOf(Page.class))); - } - - @Test - public void invokesFindAllWithPageableCorrectly() { - - Iterable result = invoker.invokeFindAll(new PageRequest(0, 10)); - assertThat(result, is(instanceOf(Page.class))); - } - - @Test - public void fallsBackToPlainFindAllIfRepositoryIsNotPaging() { - - ReflectionRepositoryInvoker invoker = new ReflectionRepositoryInvoker(orderRepository, - repositories.getRepositoryInformationFor(Order.class), conversionService); - Iterable result = invoker.invokeFindAll(new PageRequest(0, 10)); - - assertThat(result, is(instanceOf(List.class))); - } - - @Test - public void invokesQueryMethod() throws Exception { - - HashMap parameters = new HashMap(); - parameters.put("firstName", new String[] { "John" }); - - Method method = PersonRepository.class.getMethod("findByFirstName", String.class, Pageable.class); - Object result = invoker.invokeQueryMethod(method, parameters, null, null); - - assertThat(result, is(instanceOf(Page.class))); - } - - @Test - public void considersFormattingAnnotationsOnQueryMethodParameters() throws Exception { - - HashMap parameters = new HashMap(); - parameters.put("date", new String[] { "2013-07-18T10:49:00.000+02:00" }); - - Method method = PersonRepository.class.getMethod("findByCreatedUsingISO8601Date", Date.class, Pageable.class); - Object result = invoker.invokeQueryMethod(method, parameters, null, null); - - assertThat(result, is(instanceOf(Page.class))); - Page page = (Page) result; - assertThat(page.getNumberOfElements(), is(1)); - } - - /** - * @see DATAREST-325 - */ - @Test - public void invokesMethodOnPackageProtectedRepository() throws Exception { - - Object authorRepository = repositories.getRepositoryFor(Author.class); - RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(Author.class); - Method method = repositoryInformation.getRepositoryInterface().getMethod("findByFirstnameContaining", String.class); - - Map parameters = Collections.singletonMap("firstname", new String[] { "Oliver" }); - - ReflectionRepositoryInvoker invoker = new ReflectionRepositoryInvoker(authorRepository, information, - conversionService); - invoker.invokeQueryMethod(method, parameters, null, null); - } - - /** - * @see DATAREST-335, DATAREST-346 - */ - @Test - public void invokesOverriddenDeleteMethodCorrectly() { - - MyRepo repository = mock(MyRepo.class); - - MongoRepositoryFactoryBean factory = new MongoRepositoryFactoryBean(); - factory.setMongoOperations(new MongoTemplate(mock(MongoDbFactory.class))); - factory.setRepositoryInterface(MyRepo.class); - factory.setLazyInit(true); - factory.afterPropertiesSet(); - - ObjectId id = new ObjectId(); - - ReflectionRepositoryInvoker invoker = new ReflectionRepositoryInvoker(repository, - factory.getRepositoryInformation(), conversionService); - - // We must assume a non matching type here as clients might provide the raw ID value obtained from the request - invoker.invokeDelete(id.toString()); - - verify((CustomRepo) repository, times(1)).delete(id); - verify(repository, times(0)).findOne(Matchers.any(ObjectId.class)); - - } - - interface MyRepo extends CustomRepo, CrudRepository {} - - interface Domain {} - - interface CustomRepo { - void delete(ObjectId id); - } -} 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 new file mode 100644 index 000000000..e864a745c --- /dev/null +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java @@ -0,0 +1,123 @@ +/* + * 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.core.mapping; + +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.hamcrest.Matcher; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.CrudMethods; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.repository.core.support.DefaultCrudMethods; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.http.HttpMethod; + +/** + * Unit tests for {@link CrudMethodsSupportedHttpMethods}. + * + * @author Oliver Gierke + */ +@RunWith(MockitoJUnitRunner.class) +public class CrudMethodsSupportedHttpMethodsUnitTests { + + /** + * @see DATACMNS-589, DATAREST-409 + */ + @Test + public void doesNotSupportAnyHttpMethodForEmptyRepository() { + + SupportedHttpMethods supportedMethods = getSupportedHttpMethodsFor(RawRepository.class); + + assertMethodsSupported(supportedMethods, COLLECTION, true, OPTIONS); + assertMethodsSupported(supportedMethods, COLLECTION, false, GET, PUT, POST, PATCH, DELETE, HEAD); + + assertMethodsSupported(supportedMethods, ITEM, true, OPTIONS); + assertMethodsSupported(supportedMethods, ITEM, false, GET, PUT, POST, PATCH, DELETE, HEAD); + } + + /** + * @see DATAREST-217, DATAREST-330, DATACMNS-589, DATAREST-409 + */ + @Test + public void defaultsSupportedHttpMethodsForItemResource() { + + SupportedHttpMethods supportedHttpMethods = getSupportedHttpMethodsFor(SampleRepository.class); + + assertMethodsSupported(supportedHttpMethods, ITEM, true, GET, PUT, PATCH, DELETE, OPTIONS, HEAD); + assertMethodsSupported(supportedHttpMethods, ITEM, false, POST); + } + + /** + * @see DATAREST-217, DATAREST-330, DATACMNS-589, DATAREST-409 + */ + @Test + public void defaultsSupportedHttpMethodsForCollectionResource() { + + SupportedHttpMethods supportedHttpMethods = getSupportedHttpMethodsFor(SampleRepository.class); + + assertMethodsSupported(supportedHttpMethods, COLLECTION, true, GET, POST, OPTIONS, HEAD); + assertMethodsSupported(supportedHttpMethods, COLLECTION, false, PUT, PATCH, DELETE); + } + + /** + * @see DATACMNS-589, DATAREST-409 + */ + @Test + public void doesNotSupportDeleteIfDeleteMethodIsNotExported() { + + SupportedHttpMethods supportedHttpMethods = getSupportedHttpMethodsFor(HidesDelete.class); + + assertMethodsSupported(supportedHttpMethods, ITEM, false, DELETE); + } + + private static SupportedHttpMethods getSupportedHttpMethodsFor(Class repositoryInterface) { + + RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); + CrudMethods crudMethods = new DefaultCrudMethods(metadata); + + return new CrudMethodsSupportedHttpMethods(crudMethods); + } + + private static void assertMethodsSupported(SupportedHttpMethods methods, ResourceType type, boolean supported, + HttpMethod... httpMethods) { + + Matcher> isSupported = supported ? hasItems(httpMethods) : not(hasItems(httpMethods)); + + assertThat(methods.getMethodsFor(type), isSupported); + + for (HttpMethod method : httpMethods) { + assertThat(methods.supports(method, type), is(supported)); + } + } + + interface RawRepository extends Repository {} + + interface SampleRepository extends CrudRepository {} + + interface HidesDelete extends CrudRepository { + + @RestResource(exported = false) + void delete(Object id); + } +} 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 7ce689902..11719ab05 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 @@ -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(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(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); } } 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 8a3dd9f5c..83745cd55 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 @@ -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>(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()); } 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 9b8196af0..7dfbe1f5a 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 @@ -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; 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 d9592036f..f2d36daad 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 @@ -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 getSupportedMethods(ResourceType resourcType) { - - Assert.notNull(resourcType, "Resource type must not be null!"); - - if (invoker == null) { - return Collections.emptySet(); - } - - Set methods = new HashSet(); - 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 supportedMethods = getSupportedMethods(resourceType); + SupportedHttpMethods httpMethods = resourceMetadata.getSupportedHttpMethods(); + Collection supportedMethods = httpMethods.getMethodsFor(resourceType); if (!supportedMethods.contains(httpMethod)) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java index 586398442..fb937bfb8 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java @@ -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)); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java index 742d61ada..962e14195 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/PersistentEntityResourceHandlerMethodArgumentResolver.java @@ -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; 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 4f899cf22..5eddd9ce1 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 @@ -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; diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java index d20a08719..f31f04115 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RootResourceInformationHandlerMethodArgumentResolver.java @@ -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; 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 5ba242eef..7d15955bd 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 @@ -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; 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 ee96e36e6..2c35aeda2 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 @@ -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)); } } 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 index 0f644c977..1631baee0 100644 --- 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 @@ -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 */