17 changed files with 545 additions and 458 deletions
@ -0,0 +1,289 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 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.repository.support; |
||||||
|
|
||||||
|
import static org.springframework.data.repository.util.ClassUtils.*; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.lang.reflect.Type; |
||||||
|
import java.lang.reflect.TypeVariable; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||||
|
|
||||||
|
import org.springframework.data.repository.Repository; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* Default implementation of {@link RepositoryInformation}. |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
class DefaultRepositoryInformation implements RepositoryInformation { |
||||||
|
|
||||||
|
private static final TypeVariable<Class<Repository>>[] PARAMETERS = Repository.class.getTypeParameters(); |
||||||
|
private static final String DOMAIN_TYPE_NAME = PARAMETERS[0].getName(); |
||||||
|
private static final String ID_TYPE_NAME = PARAMETERS[1].getName(); |
||||||
|
|
||||||
|
private final Map<Method, Method> methodCache = new ConcurrentHashMap<Method, Method>(); |
||||||
|
|
||||||
|
private final RepositoryMetadata metadata; |
||||||
|
private final Class<?> repositoryBaseClass; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link DefaultRepositoryMetadata} for the given repository interface and repository base class. |
||||||
|
* |
||||||
|
* @param repositoryInterface |
||||||
|
*/ |
||||||
|
public DefaultRepositoryInformation(RepositoryMetadata metadata, Class<?> repositoryBaseClass) { |
||||||
|
|
||||||
|
Assert.notNull(metadata); |
||||||
|
Assert.notNull(repositoryBaseClass); |
||||||
|
this.metadata = metadata; |
||||||
|
this.repositoryBaseClass = repositoryBaseClass; |
||||||
|
} |
||||||
|
|
||||||
|
/* (non-Javadoc) |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata#getRepositoryInterface() |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Class<?> getRepositoryInterface() { |
||||||
|
return metadata.getRepositoryInterface(); |
||||||
|
} |
||||||
|
|
||||||
|
/* (non-Javadoc) |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass() |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Class<?> getDomainClass() { |
||||||
|
return metadata.getDomainClass(); |
||||||
|
} |
||||||
|
|
||||||
|
/* (non-Javadoc) |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass() |
||||||
|
*/ |
||||||
|
@Override |
||||||
|
public Class<?> getIdClass() { |
||||||
|
return metadata.getIdClass(); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* getRepositoryBaseClass() |
||||||
|
*/ |
||||||
|
public Class<?> getRepositoryBaseClass() { |
||||||
|
|
||||||
|
return this.repositoryBaseClass; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* getBaseClassMethod(java.lang.reflect.Method) |
||||||
|
*/ |
||||||
|
public Method getBaseClassMethod(Method method) { |
||||||
|
|
||||||
|
Assert.notNull(method); |
||||||
|
|
||||||
|
Method result = methodCache.get(method); |
||||||
|
|
||||||
|
if (null != result) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
result = getBaseClassMethodFor(method); |
||||||
|
methodCache.put(method, result); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the given method is considered to be a repository base class method. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean isBaseClassMethod(Method method) { |
||||||
|
|
||||||
|
Assert.notNull(method); |
||||||
|
|
||||||
|
if (method.getDeclaringClass().isAssignableFrom(repositoryBaseClass)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return !method.equals(getBaseClassMethod(method)); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* getFinderMethods() |
||||||
|
*/ |
||||||
|
public Iterable<Method> getQueryMethods() { |
||||||
|
|
||||||
|
Set<Method> result = new HashSet<Method>(); |
||||||
|
|
||||||
|
for (Method method : getRepositoryInterface().getDeclaredMethods()) { |
||||||
|
if (!isCustomMethod(method) && !isBaseClassMethod(method)) { |
||||||
|
result.add(method); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see |
||||||
|
* org.springframework.data.repository.support.RepositoryMetadata#isCustomMethod |
||||||
|
* (java.lang.reflect.Method) |
||||||
|
*/ |
||||||
|
public boolean isCustomMethod(Method method) { |
||||||
|
|
||||||
|
Class<?> declaringClass = method.getDeclaringClass(); |
||||||
|
|
||||||
|
boolean isQueryMethod = declaringClass.equals(getRepositoryInterface()); |
||||||
|
boolean isRepositoryInterface = isGenericRepositoryInterface(declaringClass); |
||||||
|
boolean isBaseClassMethod = isBaseClassMethod(method); |
||||||
|
|
||||||
|
return !(isRepositoryInterface || isBaseClassMethod || isQueryMethod); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the given base class' method if the given method (declared in the repository interface) was also declared |
||||||
|
* at the repository base class. Returns the given method if the given base class does not declare the method given. |
||||||
|
* Takes generics into account. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Method getBaseClassMethodFor(Method method) { |
||||||
|
|
||||||
|
for (Method baseClassMethod : repositoryBaseClass.getMethods()) { |
||||||
|
|
||||||
|
// Wrong name
|
||||||
|
if (!method.getName().equals(baseClassMethod.getName())) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Wrong number of arguments
|
||||||
|
if (!(method.getParameterTypes().length == baseClassMethod.getParameterTypes().length)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// Check whether all parameters match
|
||||||
|
if (!parametersMatch(method, baseClassMethod)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
return baseClassMethod; |
||||||
|
} |
||||||
|
|
||||||
|
return method; |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* (non-Javadoc) |
||||||
|
* |
||||||
|
* @see org.springframework.data.repository.support.RepositoryMetadata# |
||||||
|
* hasCustomMethod() |
||||||
|
*/ |
||||||
|
public boolean hasCustomMethod() { |
||||||
|
|
||||||
|
Class<?> repositoryInterface = getRepositoryInterface(); |
||||||
|
|
||||||
|
// No detection required if no typing interface was configured
|
||||||
|
if (isGenericRepositoryInterface(repositoryInterface)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
for (Method method : repositoryInterface.getMethods()) { |
||||||
|
if (isCustomMethod(method) && !isBaseClassMethod(method)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks the given method's parameters to match the ones of the given base class method. Matches generic arguments |
||||||
|
* agains the ones bound in the given repository interface. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @param baseClassMethod |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean parametersMatch(Method method, Method baseClassMethod) { |
||||||
|
|
||||||
|
Type[] genericTypes = baseClassMethod.getGenericParameterTypes(); |
||||||
|
Class<?>[] types = baseClassMethod.getParameterTypes(); |
||||||
|
Class<?>[] methodParameters = method.getParameterTypes(); |
||||||
|
|
||||||
|
for (int i = 0; i < genericTypes.length; i++) { |
||||||
|
|
||||||
|
Type type = genericTypes[i]; |
||||||
|
|
||||||
|
if (type instanceof TypeVariable<?>) { |
||||||
|
|
||||||
|
String name = ((TypeVariable<?>) type).getName(); |
||||||
|
|
||||||
|
if (!matchesGenericType(name, methodParameters[i])) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} else { |
||||||
|
|
||||||
|
if (!types[i].equals(methodParameters[i])) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether the given parameter type matches the generic type of the given parameter. Thus when {@literal PK} is |
||||||
|
* declared, the method ensures that given method parameter is the primary key type declared in the given repository |
||||||
|
* interface e.g. |
||||||
|
* |
||||||
|
* @param name |
||||||
|
* @param parameterType |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
private boolean matchesGenericType(String name, Class<?> parameterType) { |
||||||
|
|
||||||
|
Class<?> entityType = getDomainClass(); |
||||||
|
Class<?> idClass = getIdClass(); |
||||||
|
|
||||||
|
if (ID_TYPE_NAME.equals(name) && parameterType.equals(idClass)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
if (DOMAIN_TYPE_NAME.equals(name) && parameterType.equals(entityType)) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,75 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2011 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.repository.support; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
/** |
||||||
|
* Aditional repository specific information |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
interface RepositoryInformation extends RepositoryMetadata { |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the base class to be used to create the proxy backing instance. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Class<?> getRepositoryBaseClass(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns if the configured repository interface has custom methods, that |
||||||
|
* might have to be delegated to a custom implementation. This is used to |
||||||
|
* verify repository configuration. |
||||||
|
* |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean hasCustomMethod(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns whether the given method is a custom repository method. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @param baseClass |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
boolean isCustomMethod(Method method); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all methods considered to be query methods. |
||||||
|
* |
||||||
|
* @param repositoryInterface |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Iterable<Method> getQueryMethods(); |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the base class method that is backing the given method. This can |
||||||
|
* be necessary if a repository interface redeclares a method of the core |
||||||
|
* repository interface (e.g. for transaction behaviour customization). |
||||||
|
* Returns the method itself if the base class does not implement the given |
||||||
|
* method. |
||||||
|
* |
||||||
|
* @param method |
||||||
|
* @return |
||||||
|
*/ |
||||||
|
Method getBaseClassMethod(Method method); |
||||||
|
} |
||||||
@ -0,0 +1,63 @@ |
|||||||
|
package org.springframework.data.repository.support; |
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.*; |
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
|
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.data.repository.Repository; |
||||||
|
import org.springframework.data.repository.support.DefaultRepositoryMetadataUnitTests.DummyGenericRepositorySupport; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Oliver Gierke |
||||||
|
*/ |
||||||
|
public class DefaultRepositoryInformationUnitTests { |
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes") |
||||||
|
static final Class<DummyGenericRepositorySupport> REPOSITORY = DummyGenericRepositorySupport.class; |
||||||
|
|
||||||
|
@Test |
||||||
|
public void discoversRepositoryBaseClassMethod() throws Exception { |
||||||
|
|
||||||
|
Method method = FooDao.class.getMethod("findOne", Integer.class); |
||||||
|
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooDao.class); |
||||||
|
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, REPOSITORY); |
||||||
|
|
||||||
|
Method reference = information.getBaseClassMethodFor(method); |
||||||
|
assertEquals(REPOSITORY, reference.getDeclaringClass()); |
||||||
|
assertThat(reference.getName(), is("findOne")); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void discoveresNonRepositoryBaseClassMethod() throws Exception { |
||||||
|
|
||||||
|
Method method = FooDao.class.getMethod("findOne", Long.class); |
||||||
|
|
||||||
|
RepositoryMetadata metadata = new DefaultRepositoryMetadata(FooDao.class); |
||||||
|
DefaultRepositoryInformation information = new DefaultRepositoryInformation(metadata, Repository.class); |
||||||
|
|
||||||
|
assertThat(information.getBaseClassMethodFor(method), is(method)); |
||||||
|
} |
||||||
|
|
||||||
|
private static interface FooDao extends Repository<User, Integer> { |
||||||
|
|
||||||
|
// Redeclared method
|
||||||
|
User findOne(Integer primaryKey); |
||||||
|
|
||||||
|
// Not a redeclared method
|
||||||
|
User findOne(Long primaryKey); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
private class User { |
||||||
|
|
||||||
|
private String firstname; |
||||||
|
|
||||||
|
public String getAddress() { |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue