diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Persistable.java b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Persistable.java index 797598368..4f8c56e82 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/domain/Persistable.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/domain/Persistable.java @@ -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 the type of the identifier + * @param the type of the identifier */ -public interface Persistable extends Serializable { +public interface Persistable extends Serializable { /** * Returns the id of the entity. * * @return the id */ - PK getId(); + ID getId(); /** diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityMetadata.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityMetadata.java new file mode 100644 index 000000000..43f73433b --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AbstractEntityMetadata.java @@ -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 implements EntityMetadata { + + private final Class domainClass; + + + /** + * Creates a new {@link AbstractEntityMetadata} from the given domain class. + * + * @param domainClass + */ + public AbstractEntityMetadata(Class 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 getJavaType() { + + return this.domainClass; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/DefaultRepositoryMetadata.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/DefaultRepositoryMetadata.java new file mode 100644 index 000000000..e776bc1ed --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/DefaultRepositoryMetadata.java @@ -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>[] 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 methodCache = + new ConcurrentHashMap(); + + 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 getQueryMethods() { + + Set result = new HashSet(); + + 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; + } +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IsNewAware.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityMetadata.java similarity index 61% rename from spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IsNewAware.java rename to spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityMetadata.java index b2eadec9e..e62845cd6 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IsNewAware.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/EntityMetadata.java @@ -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 { /** * Returns whether the given entity is considered to be new. * - * @param entity + * @param entity must never be {@literal null} * @return */ - boolean isNew(Object entity); -} \ No newline at end of file + 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 getJavaType(); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IdAware.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IdAware.java deleted file mode 100644 index 02471d509..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/IdAware.java +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityMetadata.java similarity index 65% rename from spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java rename to spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityMetadata.java index 86f153316..63ba9b016 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityInformation.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/PersistableEntityMetadata.java @@ -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 { + + /** + * 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(); } } \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/ReflectiveEntityInformationSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/ReflectiveEntityInformationSupport.java deleted file mode 100644 index 4a0a1a3b3..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/ReflectiveEntityInformationSupport.java +++ /dev/null @@ -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... 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... annotations) { - - for (Class 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); - } -} \ No newline at end of file diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java index 3fe899c8d..0ce0d5b05 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactoryBeanSupport.java @@ -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> this.factory = createRepositoryFactory(); this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey); - this.factory.validate(repositoryInterface, customImplementation); for (RepositoryProxyPostProcessor processor : getRepositoryPostProcessors()) { this.factory.addRepositoryProxyPostProcessor(processor); diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java index 7f20fdf3a..567092d3f 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryFactorySupport.java @@ -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 methodCache = - new ConcurrentHashMap(); private final List postProcessors = new ArrayList(); + private QueryLookupStrategy.Key queryLookupStrategyKey; /** @@ -109,15 +102,17 @@ public abstract class RepositoryFactorySupport { * @param customImplementation * @return */ - @SuppressWarnings("unchecked") - public > T getRepository( - Class repositoryInterface, Object customImplementation) { + @SuppressWarnings({ "unchecked" }) + public T getRepository(Class 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 * @param domainClass * @return */ - protected abstract RepositorySupport getTargetRepository( - Class 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 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> 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 getFinderMethods(Class repositoryInterface) { - - Set result = new HashSet(); - - 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> 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(); 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()); } } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryMetadata.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryMetadata.java new file mode 100644 index 000000000..9d75b7862 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositoryMetadata.java @@ -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 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(); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositorySupport.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositorySupport.java deleted file mode 100644 index 93ebf69a9..000000000 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/RepositorySupport.java +++ /dev/null @@ -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 the type of entity to be handled - */ -public abstract class RepositorySupport implements - Repository { - - private final Class domainClass; - private IsNewAware isNewStrategy; - - - /** - * Creates a new {@link RepositorySupport}. - * - * @param domainClass - */ - public RepositorySupport(Class domainClass) { - - Assert.notNull(domainClass); - this.domainClass = domainClass; - } - - - /** - * Returns the domain class to handle. - * - * @return the domain class - */ - protected Class 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; - } -} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/ClassUtils.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/ClassUtils.java index 203a4eb5c..2fdabe3e9 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/ClassUtils.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/util/ClassUtils.java @@ -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>[] 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 getIdClass(Class clazz) { - - Class[] arguments = resolveTypeArguments(clazz, Repository.class); - return (Class) (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; - } } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityMetadataUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityMetadataUnitTests.java new file mode 100644 index 000000000..c8afa7aba --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AbstractEntityMetadataUnitTests.java @@ -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 metadata = + new DummyAbstractEntityMetadata(Object.class); + assertThat(metadata.isNew(null), is(true)); + assertThat(metadata.isNew(new Object()), is(false)); + } + + private static class DummyAbstractEntityMetadata extends + AbstractEntityMetadata { + + public DummyAbstractEntityMetadata(Class 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(); + } + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/DefaultRepositoryMetadataUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/DefaultRepositoryMetadataUnitTests.java new file mode 100644 index 000000000..7eb6da8d0 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/DefaultRepositoryMetadataUnitTests.java @@ -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 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 { + + } + + /** + * Sample interface to serve two purposes: + *
    + *
  1. Check that {@link ClassUtils#getDomainClass(Class)} skips non + * {@link GenericDao} interfaces
  2. + *
  3. Check that {@link ClassUtils#getDomainClass(Class)} traverses + * interface hierarchy
  4. + *
+ * + * @author Oliver Gierke + */ + private interface SomeDao extends Serializable, UserRepository { + + Page findByFirstname(Pageable pageable, String firstname); + } + + private static interface FooDao extends Repository { + + // 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 { + + } + + static abstract class DummyGenericRepositorySupport + implements Repository { + + public T findById(ID id) { + + return null; + } + } + + /** + * Helper class to reproduce #256. + * + * @author Oliver Gierke + */ + static class GenericEntity { + } + + static interface GenericEntityRepository extends + Repository, Long> { + + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityInformationTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityInformationTests.java index 16fdc8dd7..502a093e5 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityInformationTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityInformationTests.java @@ -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 void assertNewAndNoId(T info, - Object entity) { + @SuppressWarnings("rawtypes") + private > void assertNewAndNoId( + S info, Persistable entity) { assertThat(info.isNew(entity), is(true)); assertThat(info.getId(entity), is(nullValue())); } - private void assertNotNewAndId(T info, - Object entity, Object id) { + @SuppressWarnings("rawtypes") + private > void assertNotNewAndId( + S info, Persistable entity, Object id) { assertThat(info.isNew(entity), is(false)); assertThat(info.getId(entity), is(id)); diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityMetadataUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityMetadataUnitTests.java new file mode 100644 index 000000000..b22620b91 --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/PersistableEntityMetadataUnitTests.java @@ -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 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)); + } +} diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/util/ClassUtilsUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/util/ClassUtilsUnitTests.java index 8f7c9989f..e8a111f35 100644 --- a/spring-data-commons-core/src/test/java/org/springframework/data/repository/util/ClassUtilsUnitTests.java +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/util/ClassUtilsUnitTests.java @@ -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 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 { - - } - - /** - * Helper class to reproduce #256. - * - * @author Oliver Gierke - */ - static class GenericEntity { - } - - static interface GenericEntityDao extends - Repository, Long> { - - } - - /** - * Sample DAO interface to test redeclaration of {@link GenericDao} methods. - * - * @author Oliver Gierke - */ - private static interface FooDao extends Repository { - - // Redeclared method - User findById(Integer primaryKey); - - - // Not a redeclared method - User readById(Long primaryKey); - } - - static abstract class DummyGenericRepositorySupport - extends RepositorySupport { - - public DummyGenericRepositorySupport(Class domainClass) { - - super(domainClass); - } - - - public T findById(ID id) { - - return null; - } - } } diff --git a/src/main/resources/changelog.txt b/src/main/resources/changelog.txt index 3dec394a0..6378d132e 100644 --- a/src/main/resources/changelog.txt +++ b/src/main/resources/changelog.txt @@ -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) ----------------------------------------