DATACMNS-17 - Introduced metamodel for entities and repositories.
Unified IsNewAware and IdAware to EntityMetadata. Refactored base repository support to use this abstraction. Introduced RepositoryMetadata and DefaultRepositoryMetadata implementation to capture all the additional information that we need around a repository interface. Adapted factory API to use this abstraction. Opened up method parameter and return types to prepare enabling the usage of the infrastructure for interfaces that do not extend Repository interface.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -22,16 +22,16 @@ import java.io.Serializable;
|
||||
* Simple interface for entities.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @param <PK> the type of the identifier
|
||||
* @param <ID> the type of the identifier
|
||||
*/
|
||||
public interface Persistable<PK extends Serializable> extends Serializable {
|
||||
public interface Persistable<ID extends Serializable> extends Serializable {
|
||||
|
||||
/**
|
||||
* Returns the id of the entity.
|
||||
*
|
||||
* @return the id
|
||||
*/
|
||||
PK getId();
|
||||
ID getId();
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for implementations of {@link EntityMetadata}. Considers an entity
|
||||
* to be new whenever {@link #getId(Object)} returns {@literal null}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public abstract class AbstractEntityMetadata<T> implements EntityMetadata<T> {
|
||||
|
||||
private final Class<T> domainClass;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractEntityMetadata} from the given domain class.
|
||||
*
|
||||
* @param domainClass
|
||||
*/
|
||||
public AbstractEntityMetadata(Class<T> domainClass) {
|
||||
|
||||
Assert.notNull(domainClass);
|
||||
this.domainClass = domainClass;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.support.IsNewAware#isNew(java.lang
|
||||
* .Object)
|
||||
*/
|
||||
public boolean isNew(T entity) {
|
||||
|
||||
return getId(entity) == null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.support.EntityInformation#getJavaType
|
||||
* ()
|
||||
*/
|
||||
public Class<T> getJavaType() {
|
||||
|
||||
return this.domainClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
/*
|
||||
* 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.core.GenericTypeResolver.*;
|
||||
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 RepositoryMetadata}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultRepositoryMetadata implements RepositoryMetadata {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
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 Class<?> repositoryInterface;
|
||||
private final Class<?> repositoryBaseClass;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultRepositoryMetadata} for the given repository
|
||||
* interface and repository base class.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
*/
|
||||
public DefaultRepositoryMetadata(Class<?> repositoryInterface,
|
||||
Class<?> repositoryBaseClass) {
|
||||
|
||||
Assert.notNull(repositoryInterface);
|
||||
Assert.notNull(repositoryBaseClass);
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
this.repositoryBaseClass = repositoryBaseClass;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see org.springframework.data.repository.support.RepositoryMetadata#
|
||||
* getRepositoryInterface()
|
||||
*/
|
||||
public Class<?> getRepositoryInterface() {
|
||||
|
||||
return repositoryInterface;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (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#getDomainClass
|
||||
* ()
|
||||
*/
|
||||
public Class<?> getDomainClass() {
|
||||
|
||||
Class<?>[] arguments =
|
||||
resolveTypeArguments(repositoryInterface, Repository.class);
|
||||
return arguments == null ? null : arguments[0];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.support.RepositoryMetadata#getIdClass
|
||||
* ()
|
||||
*/
|
||||
public Class<?> getIdClass() {
|
||||
|
||||
Class<?>[] arguments =
|
||||
resolveTypeArguments(repositoryInterface, Repository.class);
|
||||
return arguments == null ? null : arguments[1];
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (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 : repositoryInterface.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(repositoryInterface);
|
||||
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() {
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,18 +16,34 @@
|
||||
package org.springframework.data.repository.support;
|
||||
|
||||
/**
|
||||
* Interface to abstract the ways to determine if the given entity is to be
|
||||
* considered as new.
|
||||
* Metadata for entity types.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface IsNewAware {
|
||||
public interface EntityMetadata<T> {
|
||||
|
||||
/**
|
||||
* Returns whether the given entity is considered to be new.
|
||||
*
|
||||
* @param entity
|
||||
* @param entity must never be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
boolean isNew(Object entity);
|
||||
}
|
||||
boolean isNew(T entity);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the id of the given entity.
|
||||
*
|
||||
* @param entity must never be {@literal null}
|
||||
* @return
|
||||
*/
|
||||
Object getId(T entity);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the actual domain class type.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<T> getJavaType();
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008-2010 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;
|
||||
|
||||
/**
|
||||
* Interface to abstract the ways to retrieve the id of the given entity.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface IdAware {
|
||||
|
||||
/**
|
||||
* Returns the id of the given entity.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
Object getId(Object entity);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* Copyright 2008-201 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.
|
||||
@@ -19,13 +19,26 @@ import org.springframework.data.domain.Persistable;
|
||||
|
||||
|
||||
/**
|
||||
* Implementation of {@link IsNewAware} that assumes the entity handled
|
||||
* Implementation of {@link EntityMetadata} that assumes the entity handled
|
||||
* implements {@link Persistable} and uses {@link Persistable#isNew()} for the
|
||||
* {@link #isNew(Object)} check.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class PersistableEntityInformation implements IsNewAware, IdAware {
|
||||
@SuppressWarnings("rawtypes")
|
||||
public class PersistableEntityMetadata extends
|
||||
AbstractEntityMetadata<Persistable> {
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistableEntityMetadata}.
|
||||
*
|
||||
* @param domainClass
|
||||
*/
|
||||
public PersistableEntityMetadata() {
|
||||
|
||||
super(Persistable.class);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -34,9 +47,10 @@ public class PersistableEntityInformation implements IsNewAware, IdAware {
|
||||
* org.springframework.data.repository.support.IsNewAware#isNew(java.lang
|
||||
* .Object)
|
||||
*/
|
||||
public boolean isNew(Object entity) {
|
||||
@Override
|
||||
public boolean isNew(Persistable entity) {
|
||||
|
||||
return ((Persistable<?>) entity).isNew();
|
||||
return entity.isNew();
|
||||
}
|
||||
|
||||
|
||||
@@ -47,8 +61,8 @@ public class PersistableEntityInformation implements IsNewAware, IdAware {
|
||||
* org.springframework.data.repository.support.IdAware#getId(java.lang.Object
|
||||
* )
|
||||
*/
|
||||
public Object getId(Object entity) {
|
||||
public Object getId(Persistable entity) {
|
||||
|
||||
return ((Persistable<?>) entity).getId();
|
||||
return entity.getId();
|
||||
}
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008-2010 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.annotation.Annotation;
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.ReflectionUtils.FieldCallback;
|
||||
import org.springframework.util.ReflectionUtils.MethodCallback;
|
||||
|
||||
|
||||
/**
|
||||
* {@link IsNewAware} and {@link IdAware} implementation that reflectively
|
||||
* checks a {@link Field} or {@link Method} annotated with the given
|
||||
* annotations. Subclasses usually simply have to provide the persistence
|
||||
* technology specific annotations.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class ReflectiveEntityInformationSupport implements IsNewAware, IdAware {
|
||||
|
||||
private Field field;
|
||||
private Method method;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link ReflectiveEntityInformationSupport} by inspecting
|
||||
* the given class for a {@link Field} or {@link Method} for and {@link Id}
|
||||
* annotation.
|
||||
*
|
||||
* @param domainClass not {@literal null}, must be annotated with
|
||||
* {@link Entity} and carry an anootation defining the id
|
||||
* property.
|
||||
*/
|
||||
public ReflectiveEntityInformationSupport(Class<?> domainClass,
|
||||
final Class<? extends Annotation>... annotationsToScanFor) {
|
||||
|
||||
Assert.notNull(domainClass);
|
||||
|
||||
ReflectionUtils.doWithFields(domainClass, new FieldCallback() {
|
||||
|
||||
public void doWith(Field field) {
|
||||
|
||||
if (ReflectiveEntityInformationSupport.this.field != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasAnnotation(field, annotationsToScanFor)) {
|
||||
ReflectiveEntityInformationSupport.this.field = field;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (field != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReflectionUtils.doWithMethods(domainClass, new MethodCallback() {
|
||||
|
||||
public void doWith(Method method) {
|
||||
|
||||
if (ReflectiveEntityInformationSupport.this.method != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasAnnotation(method, annotationsToScanFor)) {
|
||||
ReflectiveEntityInformationSupport.this.method = method;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Assert.isTrue(this.field != null || this.method != null,
|
||||
"No id method or field found!");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the given {@link AnnotatedElement} carries one of the
|
||||
* given {@link Annotation}s.
|
||||
*
|
||||
* @param annotatedElement
|
||||
* @param annotations
|
||||
* @return
|
||||
*/
|
||||
private boolean hasAnnotation(AnnotatedElement annotatedElement,
|
||||
Class<? extends Annotation>... annotations) {
|
||||
|
||||
for (Class<? extends Annotation> annotation : annotations) {
|
||||
|
||||
if (annotatedElement.getAnnotation(annotation) != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.support.RepositorySupport.IsNewAware
|
||||
* #isNew(java.lang.Object)
|
||||
*/
|
||||
public boolean isNew(Object entity) {
|
||||
|
||||
return getId(entity) == null;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.support.RepositorySupport.IdAware
|
||||
* #getId(java.lang.Object)
|
||||
*/
|
||||
public Object getId(Object entity) {
|
||||
|
||||
if (field != null) {
|
||||
ReflectionUtils.makeAccessible(field);
|
||||
return ReflectionUtils.getField(field, entity);
|
||||
}
|
||||
|
||||
ReflectionUtils.makeAccessible(method);
|
||||
return ReflectionUtils.invokeMethod(method, entity);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2008-2010 the original author or authors.
|
||||
* Copyright 2008-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.
|
||||
@@ -124,7 +124,6 @@ public abstract class RepositoryFactoryBeanSupport<T extends Repository<?, ?>>
|
||||
|
||||
this.factory = createRepositoryFactory();
|
||||
this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);
|
||||
this.factory.validate(repositoryInterface, customImplementation);
|
||||
|
||||
for (RepositoryProxyPostProcessor processor : getRepositoryPostProcessors()) {
|
||||
this.factory.addRepositoryProxyPostProcessor(processor);
|
||||
|
||||
@@ -15,16 +15,12 @@
|
||||
*/
|
||||
package org.springframework.data.repository.support;
|
||||
|
||||
import static org.springframework.data.repository.util.ClassUtils.*;
|
||||
import static org.springframework.util.ReflectionUtils.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
@@ -50,12 +46,9 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
public abstract class RepositoryFactorySupport {
|
||||
|
||||
private QueryLookupStrategy.Key queryLookupStrategyKey;
|
||||
|
||||
private final Map<Method, Method> methodCache =
|
||||
new ConcurrentHashMap<Method, Method>();
|
||||
private final List<RepositoryProxyPostProcessor> postProcessors =
|
||||
new ArrayList<RepositoryProxyPostProcessor>();
|
||||
private QueryLookupStrategy.Key queryLookupStrategyKey;
|
||||
|
||||
|
||||
/**
|
||||
@@ -109,15 +102,17 @@ public abstract class RepositoryFactorySupport {
|
||||
* @param customImplementation
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Repository<?, ?>> T getRepository(
|
||||
Class<T> repositoryInterface, Object customImplementation) {
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public <T> T getRepository(Class<T> repositoryInterface,
|
||||
Object customImplementation) {
|
||||
|
||||
validate(repositoryInterface, customImplementation);
|
||||
RepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(repositoryInterface,
|
||||
getRepositoryBaseClass(repositoryInterface));
|
||||
|
||||
Class<?> domainClass = getDomainClass(repositoryInterface);
|
||||
RepositorySupport<?, ?> target =
|
||||
getTargetRepository(domainClass, repositoryInterface);
|
||||
validate(metadata, customImplementation);
|
||||
|
||||
Object target = getTargetRepository(metadata);
|
||||
|
||||
// Create proxy
|
||||
ProxyFactory result = new ProxyFactory();
|
||||
@@ -128,32 +123,31 @@ public abstract class RepositoryFactorySupport {
|
||||
processor.postProcess(result);
|
||||
}
|
||||
|
||||
result.addAdvice(new QueryExecuterMethodInterceptor(
|
||||
repositoryInterface, customImplementation, target));
|
||||
result.addAdvice(new QueryExecuterMethodInterceptor(metadata,
|
||||
customImplementation, target));
|
||||
|
||||
return (T) result.getProxy();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link RepositorySupport} instance as backing for the query
|
||||
* proxy.
|
||||
* Create a repository instance as backing for the query proxy.
|
||||
*
|
||||
* @param <T>
|
||||
* @param domainClass
|
||||
* @return
|
||||
*/
|
||||
protected abstract <T, ID extends Serializable> RepositorySupport<T, ID> getTargetRepository(
|
||||
Class<T> domainClass, Class<?> repositoryInterface);
|
||||
protected abstract Object getTargetRepository(RepositoryMetadata metadata);
|
||||
|
||||
|
||||
/**
|
||||
* Determines the base class for the repository to be created.
|
||||
* Returns the base class backing the actual repository instance. Make sure
|
||||
* {@link #getTargetRepository(RepositoryMetadata)} returns an instance of
|
||||
* this class.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected abstract Class<? extends RepositorySupport> getRepositoryClass(
|
||||
protected abstract Class<?> getRepositoryBaseClass(
|
||||
Class<?> repositoryInterface);
|
||||
|
||||
|
||||
@@ -166,165 +160,23 @@ public abstract class RepositoryFactorySupport {
|
||||
protected abstract QueryLookupStrategy getQueryLookupStrategy(Key key);
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
private boolean hasCustomMethod(
|
||||
Class<? extends Repository<?, ?>> repositoryInterface) {
|
||||
|
||||
boolean hasCustomMethod = false;
|
||||
|
||||
// No detection required if no typing interface was configured
|
||||
if (isGenericRepositoryInterface(repositoryInterface)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (Method method : repositoryInterface.getMethods()) {
|
||||
|
||||
if (isCustomMethod(method, repositoryInterface)
|
||||
&& !isBaseClassMethod(method, repositoryInterface)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasCustomMethod;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given method is considered to be a repository base
|
||||
* class method.
|
||||
*
|
||||
* @param method
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private boolean isBaseClassMethod(Method method,
|
||||
Class<?> repositoryInterface) {
|
||||
|
||||
Assert.notNull(method);
|
||||
|
||||
if (method.getDeclaringClass().isAssignableFrom(
|
||||
getRepositoryClass(repositoryInterface))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !method.equals(getBaseClassMethod(method, repositoryInterface));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the base class method that is backing the given method. This can
|
||||
* be necessary if a repository interface redeclares a method in
|
||||
* {@link Repository} (e.g. for transaction behaviour customization).
|
||||
* Returns the method itself if the base class does not implement the given
|
||||
* method.
|
||||
*
|
||||
* @param method
|
||||
* @return
|
||||
*/
|
||||
private Method getBaseClassMethod(Method method,
|
||||
Class<?> repositoryInterface) {
|
||||
|
||||
Assert.notNull(method);
|
||||
|
||||
Method result = methodCache.get(method);
|
||||
|
||||
if (null != result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result =
|
||||
getBaseClassMethodFor(method,
|
||||
getRepositoryClass(repositoryInterface),
|
||||
repositoryInterface);
|
||||
methodCache.put(method, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether the given method is a custom repository method.
|
||||
*
|
||||
* @param method
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private boolean isCustomMethod(Method method, Class<?> repositoryInterface) {
|
||||
|
||||
Class<?> declaringClass = method.getDeclaringClass();
|
||||
|
||||
boolean isQueryMethod = declaringClass.equals(repositoryInterface);
|
||||
boolean isRepositoryInterface =
|
||||
isGenericRepositoryInterface(declaringClass);
|
||||
boolean isBaseClassMethod =
|
||||
isBaseClassMethod(method, repositoryInterface);
|
||||
|
||||
return !(isRepositoryInterface || isBaseClassMethod || isQueryMethod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all methods considered to be finder methods.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private Iterable<Method> getFinderMethods(Class<?> repositoryInterface) {
|
||||
|
||||
Set<Method> result = new HashSet<Method>();
|
||||
|
||||
for (Method method : repositoryInterface.getDeclaredMethods()) {
|
||||
if (!isCustomMethod(method, repositoryInterface)
|
||||
&& !isBaseClassMethod(method, repositoryInterface)) {
|
||||
result.add(method);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the given repository interface.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
*/
|
||||
private void validate(Class<?> repositoryInterface) {
|
||||
|
||||
Assert.notNull(repositoryInterface);
|
||||
Assert.notNull(
|
||||
getDomainClass(repositoryInterface),
|
||||
"Could not retrieve domain class from interface. Make sure it extends GenericRepository.");
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the given repository interface as well as the given custom
|
||||
* implementation.
|
||||
*
|
||||
* @param repositoryInterface
|
||||
* @param repositoryMetadata
|
||||
* @param customImplementation
|
||||
*/
|
||||
protected void validate(
|
||||
Class<? extends Repository<?, ?>> repositoryInterface,
|
||||
protected void validate(RepositoryMetadata repositoryMetadata,
|
||||
Object customImplementation) {
|
||||
|
||||
validate(repositoryInterface);
|
||||
|
||||
if (null == customImplementation
|
||||
&& hasCustomMethod(repositoryInterface)) {
|
||||
&& repositoryMetadata.hasCustomMethod()) {
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format(
|
||||
"You have custom methods in %s but not provided a custom implementation!",
|
||||
repositoryInterface));
|
||||
repositoryMetadata.getRepositoryInterface()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,8 +195,8 @@ public abstract class RepositoryFactorySupport {
|
||||
new ConcurrentHashMap<Method, RepositoryQuery>();
|
||||
|
||||
private final Object customImplementation;
|
||||
private final Class<?> repositoryInterface;
|
||||
private final RepositorySupport<?, ?> target;
|
||||
private final RepositoryMetadata metadata;
|
||||
private final Object target;
|
||||
|
||||
|
||||
/**
|
||||
@@ -352,18 +204,18 @@ public abstract class RepositoryFactorySupport {
|
||||
* of {@link QueryMethod}s to be invoked on execution of repository
|
||||
* interface methods.
|
||||
*/
|
||||
public QueryExecuterMethodInterceptor(Class<?> repositoryInterface,
|
||||
Object customImplementation, RepositorySupport<?, ?> target) {
|
||||
public QueryExecuterMethodInterceptor(
|
||||
RepositoryMetadata repositoryInterface,
|
||||
Object customImplementation, Object target) {
|
||||
|
||||
this.repositoryInterface = repositoryInterface;
|
||||
this.metadata = repositoryInterface;
|
||||
this.customImplementation = customImplementation;
|
||||
this.target = target;
|
||||
|
||||
QueryLookupStrategy lookupStrategy =
|
||||
getQueryLookupStrategy(queryLookupStrategyKey);
|
||||
|
||||
for (Method method : getFinderMethods(repositoryInterface)) {
|
||||
|
||||
for (Method method : metadata.getQueryMethods()) {
|
||||
queries.put(method, lookupStrategy.resolveQuery(method));
|
||||
}
|
||||
}
|
||||
@@ -393,8 +245,7 @@ public abstract class RepositoryFactorySupport {
|
||||
|
||||
// Lookup actual method as it might be redeclared in the interface
|
||||
// and we have to use the repository instance nevertheless
|
||||
Method actualMethod =
|
||||
getBaseClassMethod(method, repositoryInterface);
|
||||
Method actualMethod = metadata.getBaseClassMethod(method);
|
||||
return executeMethodOn(target, actualMethod,
|
||||
invocation.getArguments());
|
||||
}
|
||||
@@ -449,7 +300,7 @@ public abstract class RepositoryFactorySupport {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isCustomMethod(invocation.getMethod(), repositoryInterface);
|
||||
return metadata.isCustomMethod(invocation.getMethod());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
/**
|
||||
* Metadata for repository interfaces.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface RepositoryMetadata {
|
||||
|
||||
/**
|
||||
* Returns the repository interface.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> getRepositoryInterface();
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the domain class the repository is declared for.
|
||||
*
|
||||
* @param clazz
|
||||
* @return the domain class the repository is handling or {@code null} if
|
||||
* none found.
|
||||
*/
|
||||
Class<?> getDomainClass();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the id class the given class is declared for.
|
||||
*
|
||||
* @param clazz
|
||||
* @return the id class of the entity managed by the repository for or
|
||||
* {@code null} if none found.
|
||||
*/
|
||||
Class<?> getIdClass();
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008-2010 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.io.Serializable;
|
||||
|
||||
import org.springframework.data.domain.Persistable;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract base class for generic repositories. Captures information about the
|
||||
* domain class to be managed.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @param <T> the type of entity to be handled
|
||||
*/
|
||||
public abstract class RepositorySupport<T, ID extends Serializable> implements
|
||||
Repository<T, ID> {
|
||||
|
||||
private final Class<T> domainClass;
|
||||
private IsNewAware isNewStrategy;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositorySupport}.
|
||||
*
|
||||
* @param domainClass
|
||||
*/
|
||||
public RepositorySupport(Class<T> domainClass) {
|
||||
|
||||
Assert.notNull(domainClass);
|
||||
this.domainClass = domainClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the domain class to handle.
|
||||
*
|
||||
* @return the domain class
|
||||
*/
|
||||
protected Class<T> getDomainClass() {
|
||||
|
||||
return domainClass;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return whether the given entity is to be regarded as new. Default
|
||||
* implementation will inspect the given domain class and use either
|
||||
* {@link PersistableEntityInformation} if the class implements
|
||||
* {@link Persistable} or {@link ReflectiveEntityInformation} otherwise.
|
||||
*
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
protected abstract IsNewAware createIsNewStrategy(Class<?> domainClass);
|
||||
|
||||
|
||||
/**
|
||||
* Returns the strategy how to determine whether an entity is to be regarded
|
||||
* as new.
|
||||
*
|
||||
* @return the isNewStrategy
|
||||
*/
|
||||
protected IsNewAware getIsNewStrategy() {
|
||||
|
||||
if (isNewStrategy == null) {
|
||||
this.isNewStrategy = createIsNewStrategy(domainClass);
|
||||
Assert.notNull(isNewStrategy);
|
||||
}
|
||||
|
||||
return isNewStrategy;
|
||||
}
|
||||
}
|
||||
@@ -15,14 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.repository.util;
|
||||
|
||||
import static org.springframework.core.GenericTypeResolver.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.lang.reflect.TypeVariable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
@@ -38,13 +34,6 @@ import org.springframework.util.StringUtils;
|
||||
*/
|
||||
public abstract class ClassUtils {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
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 constructor to prevent instantiation.
|
||||
*/
|
||||
@@ -53,40 +42,6 @@ public abstract class ClassUtils {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the domain class the given class is declared for. Will introspect
|
||||
* the given class for extensions of {@link Repository} and retrieve the
|
||||
* domain class type from its generics declaration.
|
||||
*
|
||||
* @param clazz
|
||||
* @return the domain class the given class is repository for or
|
||||
* {@code null} if none found.
|
||||
*/
|
||||
public static Class<?> getDomainClass(Class<?> clazz) {
|
||||
|
||||
Class<?>[] arguments = resolveTypeArguments(clazz, Repository.class);
|
||||
return arguments == null ? null : arguments[0];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the id class the given class is declared for. Will introspect the
|
||||
* given class for extensions of {@link Repository} or and retrieve the
|
||||
* {@link Serializable} type from its generics declaration.
|
||||
*
|
||||
* @param clazz
|
||||
* @return the id class the given class is repository for or {@code null} if
|
||||
* none found.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Class<? extends Serializable> getIdClass(Class<?> clazz) {
|
||||
|
||||
Class<?>[] arguments = resolveTypeArguments(clazz, Repository.class);
|
||||
return (Class<? extends Serializable>) (arguments == null ? null
|
||||
: arguments[1]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the domain class returned by the given {@link Method}. Will
|
||||
* extract the type from {@link Collection}s and
|
||||
@@ -241,114 +196,4 @@ public abstract class ClassUtils {
|
||||
|
||||
throw ex;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the given base class' method if the given method (declared in the
|
||||
* interface) was also declared at the base class. Returns the given method
|
||||
* if the given base class does not declare the method given. Takes generics
|
||||
* into account.
|
||||
*
|
||||
* @param method
|
||||
* @param baseClass
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
public static Method getBaseClassMethodFor(Method method,
|
||||
Class<?> baseClass, Class<?> repositoryInterface) {
|
||||
|
||||
for (Method baseClassMethod : baseClass.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, repositoryInterface)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return baseClassMethod;
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private static boolean parametersMatch(Method method,
|
||||
Method baseClassMethod, Class<?> repositoryInterface) {
|
||||
|
||||
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],
|
||||
repositoryInterface)) {
|
||||
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
|
||||
* @param repositoryInterface
|
||||
* @return
|
||||
*/
|
||||
private static boolean matchesGenericType(String name,
|
||||
Class<?> parameterType, Class<?> repositoryInterface) {
|
||||
|
||||
Class<?> entityType = getDomainClass(repositoryInterface);
|
||||
Class<?> idClass = getIdClass(repositoryInterface);
|
||||
|
||||
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,68 @@
|
||||
/*
|
||||
* 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.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AbstractEntityMetadata}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class AbstractEntityMetadataUnitTests {
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullDomainClass() throws Exception {
|
||||
|
||||
new DummyAbstractEntityMetadata(null);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void considersEntityNewIfGetIdReturnsNull() throws Exception {
|
||||
|
||||
EntityMetadata<Object> metadata =
|
||||
new DummyAbstractEntityMetadata(Object.class);
|
||||
assertThat(metadata.isNew(null), is(true));
|
||||
assertThat(metadata.isNew(new Object()), is(false));
|
||||
}
|
||||
|
||||
private static class DummyAbstractEntityMetadata extends
|
||||
AbstractEntityMetadata<Object> {
|
||||
|
||||
public DummyAbstractEntityMetadata(Class<Object> domainClass) {
|
||||
|
||||
super(domainClass);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*
|
||||
* @see
|
||||
* org.springframework.data.repository.support.EntityMetadata#getId(
|
||||
* java.lang.Object)
|
||||
*/
|
||||
public Object getId(Object entity) {
|
||||
|
||||
return entity == null ? null : entity.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.util.ClassUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link DefaultRepositoryMetadata}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class DefaultRepositoryMetadataUnitTests {
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
static final Class<DummyGenericRepositorySupport> REPOSITORY =
|
||||
DummyGenericRepositorySupport.class;
|
||||
|
||||
|
||||
@Test
|
||||
public void looksUpDomainClassCorrectly() throws Exception {
|
||||
|
||||
RepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(UserRepository.class, REPOSITORY);
|
||||
assertEquals(User.class, metadata.getDomainClass());
|
||||
|
||||
metadata = new DefaultRepositoryMetadata(SomeDao.class, REPOSITORY);
|
||||
assertEquals(User.class, metadata.getDomainClass());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void findsDomainClassOnExtensionOfDaoInterface() throws Exception {
|
||||
|
||||
RepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(
|
||||
ExtensionOfUserCustomExtendedDao.class, REPOSITORY);
|
||||
assertEquals(User.class, metadata.getDomainClass());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void detectsParameterizedEntitiesCorrectly() {
|
||||
|
||||
RepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(GenericEntityRepository.class,
|
||||
REPOSITORY);
|
||||
assertEquals(GenericEntity.class, metadata.getDomainClass());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void looksUpIdClassCorrectly() throws Exception {
|
||||
|
||||
RepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(UserRepository.class, REPOSITORY);
|
||||
|
||||
assertEquals(Integer.class, metadata.getIdClass());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void discoversRepositoryBaseClassMethod() throws Exception {
|
||||
|
||||
Method method = FooDao.class.getMethod("findById", Integer.class);
|
||||
DefaultRepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(FooDao.class, REPOSITORY);
|
||||
|
||||
Method reference = metadata.getBaseClassMethodFor(method);
|
||||
assertEquals(REPOSITORY, reference.getDeclaringClass());
|
||||
assertThat(reference.getName(), is("findById"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void discoveresNonRepositoryBaseClassMethod() throws Exception {
|
||||
|
||||
Method method = FooDao.class.getMethod("readById", Long.class);
|
||||
|
||||
DefaultRepositoryMetadata metadata =
|
||||
new DefaultRepositoryMetadata(FooDao.class, Repository.class);
|
||||
|
||||
assertThat(metadata.getBaseClassMethodFor(method), is(method));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private class User {
|
||||
|
||||
private String firstname;
|
||||
|
||||
|
||||
public String getAddress() {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static interface UserRepository extends Repository<User, Integer> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample interface to serve two purposes:
|
||||
* <ol>
|
||||
* <li>Check that {@link ClassUtils#getDomainClass(Class)} skips non
|
||||
* {@link GenericDao} interfaces</li>
|
||||
* <li>Check that {@link ClassUtils#getDomainClass(Class)} traverses
|
||||
* interface hierarchy</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private interface SomeDao extends Serializable, UserRepository {
|
||||
|
||||
Page<User> findByFirstname(Pageable pageable, String firstname);
|
||||
}
|
||||
|
||||
private static interface FooDao extends Repository<User, Integer> {
|
||||
|
||||
// Redeclared method
|
||||
User findById(Integer primaryKey);
|
||||
|
||||
|
||||
// Not a redeclared method
|
||||
User readById(Long primaryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample interface to test recursive lookup of domain class.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static interface ExtensionOfUserCustomExtendedDao extends
|
||||
UserCustomExtendedRepository {
|
||||
|
||||
}
|
||||
|
||||
static interface UserCustomExtendedRepository extends
|
||||
Repository<User, Integer> {
|
||||
|
||||
}
|
||||
|
||||
static abstract class DummyGenericRepositorySupport<T, ID extends Serializable>
|
||||
implements Repository<T, ID> {
|
||||
|
||||
public T findById(ID id) {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to reproduce #256.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class GenericEntity<T> {
|
||||
}
|
||||
|
||||
static interface GenericEntityRepository extends
|
||||
Repository<GenericEntity<String>, Long> {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@ import org.springframework.data.domain.Persistable;
|
||||
|
||||
|
||||
/**
|
||||
* Unit test for {@link PersistableEntityInformation}.
|
||||
* Unit test for {@link PersistableEntityMetadata}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@@ -32,23 +32,25 @@ public class PersistableEntityInformationTests {
|
||||
@Test
|
||||
public void detectsPersistableCorrectly() throws Exception {
|
||||
|
||||
PersistableEntityInformation info = new PersistableEntityInformation();
|
||||
PersistableEntityMetadata info = new PersistableEntityMetadata();
|
||||
|
||||
assertNewAndNoId(info, new PersistableEntity(null));
|
||||
assertNotNewAndId(info, new PersistableEntity(1L), 1L);
|
||||
}
|
||||
|
||||
|
||||
private <T extends IdAware & IsNewAware> void assertNewAndNoId(T info,
|
||||
Object entity) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
private <S extends EntityMetadata<Persistable>> void assertNewAndNoId(
|
||||
S info, Persistable entity) {
|
||||
|
||||
assertThat(info.isNew(entity), is(true));
|
||||
assertThat(info.getId(entity), is(nullValue()));
|
||||
}
|
||||
|
||||
|
||||
private <T extends IdAware & IsNewAware> void assertNotNewAndId(T info,
|
||||
Object entity, Object id) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
private <S extends EntityMetadata<Persistable>> void assertNotNewAndId(
|
||||
S info, Persistable entity, Object id) {
|
||||
|
||||
assertThat(info.isNew(entity), is(false));
|
||||
assertThat(info.getId(entity), is(id));
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.domain.Persistable;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PersistableEntityMetadata}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class PersistableEntityMetadataUnitTests {
|
||||
|
||||
static final PersistableEntityMetadata metadata =
|
||||
new PersistableEntityMetadata();
|
||||
|
||||
@Mock
|
||||
Persistable<Long> persistable;
|
||||
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("serial")
|
||||
public void usesPersistablesGetId() throws Exception {
|
||||
|
||||
when(persistable.getId()).thenReturn(2L, 1L, 3L);
|
||||
assertEquals(2L, metadata.getId(persistable));
|
||||
assertEquals(1L, metadata.getId(persistable));
|
||||
assertEquals(3L, metadata.getId(persistable));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void usesPersistablesIsNew() throws Exception {
|
||||
|
||||
when(persistable.isNew()).thenReturn(true, false);
|
||||
assertThat(metadata.isNew(persistable), is(true));
|
||||
assertThat(metadata.isNew(persistable), is(false));
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,15 @@
|
||||
*/
|
||||
package org.springframework.data.repository.util;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.data.repository.util.ClassUtils.*;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.support.RepositorySupport;
|
||||
|
||||
|
||||
/**
|
||||
@@ -36,23 +33,6 @@ import org.springframework.data.repository.support.RepositorySupport;
|
||||
*/
|
||||
public class ClassUtilsUnitTests {
|
||||
|
||||
@Test
|
||||
public void looksUpDomainClassCorrectly() throws Exception {
|
||||
|
||||
assertEquals(User.class, getDomainClass(UserRepository.class));
|
||||
assertEquals(User.class, getDomainClass(SomeDao.class));
|
||||
assertNull(getDomainClass(Serializable.class));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void looksUpIdClassCorrectly() throws Exception {
|
||||
|
||||
assertEquals(Integer.class, getIdClass(UserRepository.class));
|
||||
assertNull(getIdClass(Serializable.class));
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void rejectsInvalidReturnType() throws Exception {
|
||||
|
||||
@@ -61,14 +41,6 @@ public class ClassUtilsUnitTests {
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void findsDomainClassOnExtensionOfDaoInterface() throws Exception {
|
||||
|
||||
assertEquals(User.class,
|
||||
getDomainClass(ExtensionOfUserCustomExtendedDao.class));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void determinesValidFieldsCorrectly() {
|
||||
|
||||
@@ -77,48 +49,6 @@ public class ClassUtilsUnitTests {
|
||||
assertFalse(hasProperty(User.class, "address"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* References #256.
|
||||
*/
|
||||
@Test
|
||||
public void detectsParameterizedEntitiesCorrectly() {
|
||||
|
||||
assertEquals(GenericEntity.class,
|
||||
getDomainClass(GenericEntityDao.class));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #301
|
||||
*/
|
||||
@Test
|
||||
public void discoversDaoBaseClassMethod() throws Exception {
|
||||
|
||||
Method method = FooDao.class.getMethod("findById", Integer.class);
|
||||
|
||||
Method reference =
|
||||
getBaseClassMethodFor(method,
|
||||
DummyGenericRepositorySupport.class, FooDao.class);
|
||||
assertEquals(DummyGenericRepositorySupport.class,
|
||||
reference.getDeclaringClass());
|
||||
assertThat(reference.getName(), is("findById"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* #301
|
||||
*/
|
||||
@Test
|
||||
public void discoveresNonDaoBaseClassMethod() throws Exception {
|
||||
|
||||
Method method = FooDao.class.getMethod("readById", Long.class);
|
||||
|
||||
assertThat(
|
||||
getBaseClassMethodFor(method, RepositorySupport.class,
|
||||
FooDao.class), is(method));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private class User {
|
||||
|
||||
@@ -150,62 +80,4 @@ public class ClassUtilsUnitTests {
|
||||
|
||||
Page<User> findByFirstname(Pageable pageable, String firstname);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample interface to test recursive lookup of domain class.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static interface ExtensionOfUserCustomExtendedDao extends
|
||||
UserCustomExtendedRepository {
|
||||
|
||||
}
|
||||
|
||||
static interface UserCustomExtendedRepository extends
|
||||
Repository<User, Integer> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class to reproduce #256.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
static class GenericEntity<T> {
|
||||
}
|
||||
|
||||
static interface GenericEntityDao extends
|
||||
Repository<GenericEntity<String>, Long> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample DAO interface to test redeclaration of {@link GenericDao} methods.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static interface FooDao extends Repository<User, Integer> {
|
||||
|
||||
// Redeclared method
|
||||
User findById(Integer primaryKey);
|
||||
|
||||
|
||||
// Not a redeclared method
|
||||
User readById(Long primaryKey);
|
||||
}
|
||||
|
||||
static abstract class DummyGenericRepositorySupport<T, ID extends Serializable>
|
||||
extends RepositorySupport<T, ID> {
|
||||
|
||||
public DummyGenericRepositorySupport(Class<T> domainClass) {
|
||||
|
||||
super(domainClass);
|
||||
}
|
||||
|
||||
|
||||
public T findById(ID id) {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ Changes in version 1.0.0.M4
|
||||
|
||||
Repository
|
||||
* Improved ParameterAccessor infrastructure
|
||||
* Added support for 'Distinct' keyword in finder method names
|
||||
* Added support for 'In' and 'NotIn' keywords
|
||||
* Added support for 'Distinct' keyword in finder method names (DATACMNS-15)
|
||||
* Added support for 'In' and 'NotIn' keywords (DATACMNS-16)
|
||||
* Introduced metamodel for entities and repositories (DATACMNS-17)
|
||||
|
||||
Changes in version 1.0.0.M3 (2011-02-09)
|
||||
----------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user