DATACMNS-37 - Enabled annotation based repository declaration model.
Introduced an @RepositoryProxy annotation that can be used as alternative to extending Repository. Added necessary RepositoryMetadata implementation to introspect the annotation rather than the generic types. Enabled bean definition parsers to pick up annotation based repositories as well.
This commit is contained in:
@@ -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<? extends Serializable> idClass();
|
||||
}
|
||||
@@ -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 AbstractRepositoryConfigDefinitionParser<S extends GlobalR
|
||||
|
||||
super(false);
|
||||
addIncludeFilter(new InterfaceTypeFilter(repositoryInterface));
|
||||
addIncludeFilter(new AnnotationTypeFilter(RepositoryProxy.class, true, true));
|
||||
addExcludeFilter(new AnnotationTypeFilter(NoRepositoryBean.class));
|
||||
}
|
||||
|
||||
@@ -476,5 +481,85 @@ public abstract class AbstractRepositoryConfigDefinitionParser<S extends GlobalR
|
||||
&& super.match(metadataReader, metadataReaderFactory);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy of Spring's AnnotationTypeFilter until SPR-8336 gets resolved.
|
||||
|
||||
/**
|
||||
* A simple filter which matches classes with a given annotation,
|
||||
* checking inherited annotations as well.
|
||||
*
|
||||
* <p>The matching logic mirrors that of <code>Class.isAnnotationPresent()</code>.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Ramnivas Laddad
|
||||
* @author Juergen Hoeller
|
||||
* @since 2.5
|
||||
*/
|
||||
private static class AnnotationTypeFilter extends AbstractTypeHierarchyTraversingFilter {
|
||||
|
||||
private final Class<? extends Annotation> 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
|
||||
* '<code>considerMetaAnnotations</code>' argument. The filter will
|
||||
* not match interfaces.
|
||||
* @param annotationType the annotation type to match
|
||||
*/
|
||||
public AnnotationTypeFilter(Class<? extends Annotation> 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<? extends Annotation> 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<? extends Annotation> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user