diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/RepositoryProxy.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/RepositoryProxy.java new file mode 100644 index 000000000..ab208f035 --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/RepositoryProxy.java @@ -0,0 +1,39 @@ +package org.springframework.data.repository; + +import java.io.Serializable; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to demarcate interfaces a repository proxy shall be created for. Annotating an interface with + * {@link RepositoryProxy} will cause the same behaviour as extending {@link Repository}. + * + * @see Repository + * @author Oliver Gierke + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface RepositoryProxy { + + /** + * The domain class the repository manages. Equivalent to the T type parameter in {@link Repository}. + * + * @see Repository + * @return + */ + Class domainClass(); + + /** + * The id class of the entity the repository manages. Equivalent to the ID type parameter in {@link Repository}. + * + * @see Repository + * @return + */ + Class idClass(); +} diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java index 5417b006d..bca6441ae 100644 --- a/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/config/AbstractRepositoryConfigDefinitionParser.java @@ -19,6 +19,8 @@ import static org.springframework.beans.factory.support.BeanDefinitionReaderUtil import static org.springframework.data.repository.util.ClassUtils.*; import java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.annotation.Inherited; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; @@ -36,12 +38,14 @@ import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.AbstractTypeHierarchyTraversingFilter; import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.core.type.filter.RegexPatternTypeFilter; import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.RepositoryProxy; import org.springframework.util.StringUtils; import org.w3c.dom.Element; @@ -414,6 +418,7 @@ public abstract class AbstractRepositoryConfigDefinitionParserThe matching logic mirrors that of Class.isAnnotationPresent(). + * + * @author Mark Fisher + * @author Ramnivas Laddad + * @author Juergen Hoeller + * @since 2.5 + */ + private static class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter { + + private final Class annotationType; + + private final boolean considerMetaAnnotations; + + + /** + * Create a new AnnotationTypeFilter for the given annotation type. + * This filter will also match meta-annotations. To disable the + * meta-annotation matching, use the constructor that accepts a + * 'considerMetaAnnotations' argument. The filter will + * not match interfaces. + * @param annotationType the annotation type to match + */ + public AnnotationTypeFilter(Class annotationType) { + this(annotationType, true); + } + + /** + * Create a new AnnotationTypeFilter for the given annotation type. + * The filter will not match interfaces. + * @param annotationType the annotation type to match + * @param considerMetaAnnotations whether to also match on meta-annotations + */ + public AnnotationTypeFilter(Class annotationType, boolean considerMetaAnnotations) { + this(annotationType, considerMetaAnnotations, false); + } + + /** + * Create a new {@link AnnotationTypeFilter} for the given annotation type. + * @param annotationType the annotation type to match + * @param considerMetaAnnotations whether to also match on meta-annotations + * @param considerInterfaces whether to also match interfaces + */ + public AnnotationTypeFilter(Class annotationType, boolean considerMetaAnnotations, boolean considerInterfaces) { + super(annotationType.isAnnotationPresent(Inherited.class), considerInterfaces); + this.annotationType = annotationType; + this.considerMetaAnnotations = considerMetaAnnotations; + } + + + @Override + protected boolean matchSelf(MetadataReader metadataReader) { + AnnotationMetadata metadata = metadataReader.getAnnotationMetadata(); + return metadata.hasAnnotation(this.annotationType.getName()) || + (this.considerMetaAnnotations && metadata.hasMetaAnnotation(this.annotationType.getName())); + } + + @Override + protected Boolean matchSuperClass(String superClassName) { + if (Object.class.getName().equals(superClassName)) { + return Boolean.FALSE; + } + else if (superClassName.startsWith("java.")) { + try { + Class clazz = getClass().getClassLoader().loadClass(superClassName); + return (clazz.getAnnotation(this.annotationType) != null); + } + catch (ClassNotFoundException ex) { + // Class not found - can't determine a match that way. + } + } + return null; + } + } } } diff --git a/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AnnotationRepositoryMetadata.java b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AnnotationRepositoryMetadata.java new file mode 100644 index 000000000..0f48c783c --- /dev/null +++ b/spring-data-commons-core/src/main/java/org/springframework/data/repository/support/AnnotationRepositoryMetadata.java @@ -0,0 +1,50 @@ +package org.springframework.data.repository.support; + +import org.springframework.data.repository.RepositoryProxy; +import org.springframework.util.Assert; + +/** + * {@link RepositoryMetadata} implementation inspecting the given repository interface for a {@link RepositoryProxy} + * annotation. + * + * @author Oliver Gierke + */ +public class AnnotationRepositoryMetadata implements RepositoryMetadata { + + private static final String NO_ANNOTATION_FOUND = String.format("Interface must be annotated with @%s!", + RepositoryProxy.class.getName()); + + private final Class repositoryInterface; + + public AnnotationRepositoryMetadata(Class repositoryInterface) { + Assert.notNull(repositoryInterface, "Repository interface must not be null!"); + Assert.isTrue(repositoryInterface.isAnnotationPresent(RepositoryProxy.class), NO_ANNOTATION_FOUND); + this.repositoryInterface = repositoryInterface; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass() + */ + public Class getIdClass() { + RepositoryProxy annotation = repositoryInterface.getAnnotation(RepositoryProxy.class); + return annotation == null ? null : annotation.idClass(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass() + */ + public Class getDomainClass() { + RepositoryProxy annotation = repositoryInterface.getAnnotation(RepositoryProxy.class); + return annotation == null ? null : annotation.domainClass(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryMetadata#getRepositoryInterface() + */ + public Class getRepositoryInterface() { + return repositoryInterface; + } +} 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 index de72a340b..816415845 100644 --- 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 @@ -20,10 +20,10 @@ import static org.springframework.core.GenericTypeResolver.*; import org.springframework.data.repository.Repository; import org.springframework.util.Assert; - /** - * Default implementation of {@link RepositoryMetadata}. - * + * Default implementation of {@link RepositoryMetadata}. Will inspect generic types of + * {@link Repository} to find out about domain and id class. + * * @author Oliver Gierke */ public class DefaultRepositoryMetadata implements RepositoryMetadata { @@ -33,23 +33,22 @@ public class DefaultRepositoryMetadata implements RepositoryMetadata { /** * Creates a new {@link DefaultRepositoryMetadata} for the given repository - * interface and repository base class. + * interface. * * @param repositoryInterface */ public DefaultRepositoryMetadata(Class repositoryInterface) { Assert.notNull(repositoryInterface); + Assert.isTrue(Repository.class.isAssignableFrom(repositoryInterface)); this.repositoryInterface = repositoryInterface; } /* - * (non-Javadoc) - * - * @see org.springframework.data.repository.support.RepositoryMetadata# - * getRepositoryInterface() - */ + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryMetadata#getRepositoryInterface() + */ public Class getRepositoryInterface() { return repositoryInterface; @@ -57,14 +56,11 @@ public class DefaultRepositoryMetadata implements RepositoryMetadata { /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.support.RepositoryMetadata#getDomainClass - * () - */ + * (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]; @@ -72,12 +68,9 @@ public class DefaultRepositoryMetadata implements RepositoryMetadata { /* - * (non-Javadoc) - * - * @see - * org.springframework.data.repository.support.RepositoryMetadata#getIdClass - * () - */ + * (non-Javadoc) + * @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass() + */ public Class getIdClass() { Class[] arguments = 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 883033a9e..bbe9a514e 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 @@ -153,7 +153,8 @@ public abstract class RepositoryFactorySupport { * @return */ RepositoryMetadata getRepositoryMetadata(Class repositoryInterface) { - return new DefaultRepositoryMetadata(repositoryInterface); + return Repository.class.isAssignableFrom(repositoryInterface) ? new DefaultRepositoryMetadata(repositoryInterface) + : new AnnotationRepositoryMetadata(repositoryInterface); } diff --git a/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AnnotationRepositoryMetadataUnitTests.java b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AnnotationRepositoryMetadataUnitTests.java new file mode 100644 index 000000000..9ca5d732b --- /dev/null +++ b/spring-data-commons-core/src/test/java/org/springframework/data/repository/support/AnnotationRepositoryMetadataUnitTests.java @@ -0,0 +1,65 @@ +/* + * 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.junit.Assert.*; + +import org.junit.Test; +import org.springframework.data.repository.RepositoryProxy; + + +/** + * Unit tests for {@link DefaultRepositoryMetadata}. + * + * @author Oliver Gierke + */ +public class AnnotationRepositoryMetadataUnitTests { + + @Test + public void handlesRepositoryProxyAnnotationCorrectly() { + + RepositoryMetadata metadata = new AnnotationRepositoryMetadata(AnnotatedRepository.class); + assertEquals(User.class, metadata.getDomainClass()); + assertEquals(Integer.class, metadata.getIdClass()); + } + + @Test(expected = IllegalArgumentException.class) + public void preventsUnannotatedInterface() { + + new AnnotationRepositoryMetadata(UnannotatedRepository.class); + } + + @SuppressWarnings("unused") + private class User { + + private String firstname; + + + public String getAddress() { + + return null; + } + } + + @RepositoryProxy(domainClass = User.class, idClass = Integer.class) + interface AnnotatedRepository { + + } + + interface UnannotatedRepository { + + } +} 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 index 49c99a47d..117993286 100644 --- 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 @@ -15,6 +15,7 @@ */ package org.springframework.data.repository.support; +import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; import java.io.Serializable; @@ -23,6 +24,7 @@ import org.junit.Test; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.RepositoryProxy; import org.springframework.data.repository.util.ClassUtils; @@ -73,7 +75,6 @@ public class DefaultRepositoryMetadataUnitTests { assertEquals(Integer.class, metadata.getIdClass()); } - @SuppressWarnings("unused") private class User {