DATACMNS-393 - Improved CRUD method detection in DefaultCrudMethods.

The detection of CRUD methods in DefaultCrudMethods is now carried out in a more robust and predictable fashion. The exact algorithm is described in the JavaDoc.

Original pull request: #55.
This commit is contained in:
Thomas Darimont
2013-11-06 00:46:13 +01:00
committed by Oliver Gierke
parent 5a6cefc4a9
commit 907ea74d8a
2 changed files with 240 additions and 74 deletions

View File

@@ -15,16 +15,18 @@
*/
package org.springframework.data.repository.core.support;
import java.io.Serializable;
import java.lang.reflect.Method;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.core.CrudMethods;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
@@ -33,18 +35,20 @@ import org.springframework.util.ReflectionUtils;
* {@link CrudRepository}.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @since 1.6
*/
class DefaultCrudMethods implements CrudMethods {
private final RepositoryInformation information;
private static final String FIND_ONE = "findOne";
private static final String SAVE = "save";
private static final String FIND_ALL = "findAll";
private static final String DELETE = "delete";
private Method findAllMethod;
private boolean findAllHasPaging;
private Method findOneMethod;
private Method saveMethod;
private Method deleteMethod;
private final Method findAllMethod;
private final Method findOneMethod;
private final Method saveMethod;
private final Method deleteMethod;
/**
* Creates a new {@link DefaultCrudMethods} using the given {@link RepositoryInformation}.
@@ -53,86 +57,137 @@ class DefaultCrudMethods implements CrudMethods {
*/
public DefaultCrudMethods(RepositoryInformation information) {
Assert.notNull(information, "RepositoryInformation must not be null!");
this.information = information;
Assert.notNull(information, "information must not be null!");
for (Method method : ReflectionUtils.getAllDeclaredMethods(information.getRepositoryInterface())) {
if (!information.isBaseClassMethod(method)) {
continue;
}
if (method.getName().equals("findAll")) {
findAllDetected(method);
continue;
}
if (method.getName().equals("findOne")) {
this.findOneMethod = method;
}
if (method.getName().equals("save")) {
this.saveMethod = method;
}
if (method.getName().equals("delete")) {
deleteDetected(method);
}
}
this.findOneMethod = selectMostSuitableFindOneMethod(information);
this.findAllMethod = selectMostSuitableFindAllMethod(information);
this.deleteMethod = selectMostSuitableDeleteMethod(information);
this.saveMethod = selectMostSuitableSaveMethod(information);
}
/**
* Checks whether the given method is a more usable find all method. Will prefer methods taking a {@link Pageable} and
* sort over a simple one.
* The most suitable save method is selected as follows: We prefer
* <ol>
* <li>a {@link RepositoryMetadata#getDomainType()} as first parameter over
* <li>
* <li>an {@link Object} as first parameter
* <li>
* </ol>
*
* @param method
* @param information must not be {@literal null}.
* @return the most suitable method or {@literal null} if no method could be found.
*/
private void findAllDetected(Method method) {
private Method selectMostSuitableSaveMethod(RepositoryInformation information) {
if (findAllMethod != null && findAllHasPaging) {
return;
}
Assert.notNull(information, "information must not be null!");
Class<?>[] parameterType = method.getParameterTypes();
if (parameterType.length > 0) {
if (parameterType[0].equals(Pageable.class)) {
this.findAllMethod = method;
this.findAllHasPaging = true;
return;
}
if (parameterType[0].equals(Sort.class)) {
this.findAllMethod = method;
}
return;
}
if (findAllMethod == null) {
this.findAllMethod = method;
}
Method saveMethodCandidate = ReflectionUtils.findMethod(information.getRepositoryInterface(), SAVE,
information.getDomainType());
return ClassUtils.getMostSpecificMethod(
saveMethodCandidate != null ? saveMethodCandidate : ReflectionUtils.findMethod(
information.getRepositoryInterface(), SAVE, Object.class), information.getRepositoryInterface());
}
/**
* Checks whether the given method is a more usable delete method. Will prefer delete-by-id methods over
* delete-by-instance ones.
* The most suitable delete method is selected as follows: We prefer
* <ol>
* <li>a {@link RepositoryMetadata#getIdType()} as first parameter over
* <li>
* <li>a {@link Serializable} as first parameter over
* <li>
* <li>an {@link Iterable} as first parameter
* <li>
* </ol>
*
* @param method
* @param information must not be {@literal null}.
* @return the most suitable method or {@literal null} if no method could be found.
*/
private void deleteDetected(Method method) {
private Method selectMostSuitableDeleteMethod(RepositoryInformation information) {
MethodParameter parameter = new MethodParameter(method, 0);
Class<?> parameterType = GenericTypeResolver.resolveParameterType(parameter, information.getRepositoryInterface());
Assert.notNull(information, "information must not be null!");
if (information.getIdType().isAssignableFrom(parameterType)) {
this.deleteMethod = method;
Method deleteMethodCandiate = ReflectionUtils.findMethod(information.getRepositoryInterface(), DELETE,
information.getIdType());
deleteMethodCandiate = deleteMethodCandiate != null ? deleteMethodCandiate : ReflectionUtils.findMethod(
information.getRepositoryInterface(), DELETE, Serializable.class);
deleteMethodCandiate = deleteMethodCandiate != null ? deleteMethodCandiate : ReflectionUtils.findMethod(
information.getRepositoryInterface(), DELETE, Iterable.class);
return ClassUtils.getMostSpecificMethod(deleteMethodCandiate, information.getRepositoryInterface());
}
/**
* The most suitable findAll method is selected as follows: We prefer
* <ol>
* <li>a {@link Pageable} as first parameter over
* <li>
* <li>a {@link Sort} as first parameter over
* <li>
* <li>no parameters
* <li>
* </ol>
*
* @param information must not be {@literal null}.
* @return the most suitable method or {@literal null} if no method could be found.
*/
private Method selectMostSuitableFindAllMethod(RepositoryInformation information) {
Assert.notNull(information, "information must not be null!");
Method findAllCandidateMethod = null;
if (ClassUtils.hasMethod(information.getRepositoryInterface(), FIND_ALL)) {
findAllCandidateMethod = ClassUtils.getMostSpecificMethod(
ReflectionUtils.findMethod(CrudRepository.class, FIND_ALL), information.getRepositoryInterface());
}
if (this.deleteMethod == null) {
this.deleteMethod = method;
Method findAllPageableCandidateMethod = null;
if (ClassUtils.hasMethod(information.getRepositoryInterface(), FIND_ALL, Pageable.class)) {
findAllPageableCandidateMethod = ClassUtils.getMostSpecificMethod(
ReflectionUtils.findMethod(PagingAndSortingRepository.class, FIND_ALL, Pageable.class),
information.getRepositoryInterface());
}
Method findAllSortCandidateMethod = null;
if (ClassUtils.hasMethod(information.getRepositoryInterface(), FIND_ALL, Sort.class)) {
findAllSortCandidateMethod = ClassUtils.getMostSpecificMethod(
ReflectionUtils.findMethod(PagingAndSortingRepository.class, FIND_ALL, Sort.class),
information.getRepositoryInterface());
}
if (findAllPageableCandidateMethod != null) {
return findAllPageableCandidateMethod;
}
if (findAllSortCandidateMethod != null) {
return findAllSortCandidateMethod;
}
return findAllCandidateMethod;
}
/**
* The most suitable findOne method is selected as follows: We prefer
* <ol>
* <li>a {@link RepositoryMetadata#getIdType()} as first parameter over
* <li>
* <li>a {@link Serializable} as first parameter
* <li>
* </ol>
*
* @param information must not be {@literal null}.
* @return the most suitable method or {@literal null} if no method could be found.
*/
private Method selectMostSuitableFindOneMethod(RepositoryInformation information) {
Assert.notNull(information, "information must not be null!");
Method findOneMethodCandidate = ReflectionUtils.findMethod(information.getRepositoryInterface(), FIND_ONE,
information.getIdType());
return ClassUtils.getMostSpecificMethod(
findOneMethodCandidate != null ? findOneMethodCandidate : ReflectionUtils.findMethod(
information.getRepositoryInterface(), FIND_ONE, Serializable.class), information.getRepositoryInterface());
}
/* (non-Javadoc)