DATACMNS-90 - Allow Repository namespace handler to be configured to scan for inner class repositories.

We now optionally consider nested repository interfaces defined as inner-classes. This can be configured by setting the considerNestedRepositories property on EnableXXXRepository annotations or the consider-nested-repositories attribute to true - it defaults to false.

Original pull request: #50.
This commit is contained in:
Thomas Darimont
2013-10-15 16:35:10 +02:00
committed by Oliver Gierke
parent e76f820469
commit 60534974bf
11 changed files with 376 additions and 14 deletions

View File

@@ -48,6 +48,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
private static final String NAMED_QUERIES_LOCATION = "namedQueriesLocation";
private static final String QUERY_LOOKUP_STRATEGY = "queryLookupStrategy";
private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass";
private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories";
private final AnnotationMetadata metadata;
private final AnnotationAttributes attributes;
@@ -222,4 +223,12 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
}
return typeFilters;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#isConsideringNestedRepositoriesEnabled()
*/
@Override
public boolean isConsideringNestedRepositoriesEnabled() {
return attributes.getBoolean(CONSIDER_NESTED_REPOSITORIES);
}
}

View File

@@ -1,3 +1,18 @@
/*
* Copyright 2012-2013 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.config;
import java.io.IOException;
@@ -26,6 +41,8 @@ import org.springframework.util.Assert;
*/
class RepositoryComponentProvider extends ClassPathScanningCandidateComponentProvider {
private boolean considerNestedRepositoryInterfaces;
/**
* Creates a new {@link RepositoryComponentProvider} using the given {@link TypeFilter} to include components to be
* picked up.
@@ -83,8 +100,26 @@ class RepositoryComponentProvider extends ClassPathScanningCandidateComponentPro
boolean isNonRepositoryInterface = !ClassUtils.isGenericRepositoryInterface(beanDefinition.getBeanClassName());
boolean isTopLevelType = !beanDefinition.getMetadata().hasEnclosingClass();
boolean isConsiderNestedRepositories = isConsiderNestedRepositoryInterfaces();
return isNonRepositoryInterface && isTopLevelType;
return isNonRepositoryInterface && (isTopLevelType || isConsiderNestedRepositories);
}
/**
* @return the considerNestedRepositoryInterfaces
*/
public boolean isConsiderNestedRepositoryInterfaces() {
return considerNestedRepositoryInterfaces;
}
/**
* Controls whether nested inner-class {@link Repository} interface definitions should be considered for automatic
* discovery. This defaults to {@literal false}.
*
* @param considerNestedRepositoryInterfaces
*/
public void setConsiderNestedRepositoryInterfaces(boolean considerNestedRepositoryInterfaces) {
this.considerNestedRepositoryInterfaces = considerNestedRepositoryInterfaces;
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 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.
@@ -25,6 +25,7 @@ import org.springframework.data.repository.query.QueryLookupStrategy;
* Interface containing the configurable options for the Spring Data repository subsystem.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public interface RepositoryConfigurationSource {
@@ -76,4 +77,9 @@ public interface RepositoryConfigurationSource {
* @return
*/
Collection<String> getCandidates(ResourceLoader loader);
/**
* @return true if the container should look for nested repository interface definitions.
*/
boolean isConsideringNestedRepositoriesEnabled();
}

View File

@@ -21,7 +21,6 @@ import java.util.HashSet;
import java.util.Set;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.ResourceLoader;
@@ -54,7 +53,8 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository
*/
public Collection<String> getCandidates(ResourceLoader loader) {
ClassPathScanningCandidateComponentProvider scanner = new RepositoryComponentProvider(getIncludeFilters());
RepositoryComponentProvider scanner = new RepositoryComponentProvider(getIncludeFilters());
scanner.setConsiderNestedRepositoryInterfaces(isConsideringNestedRepositoriesEnabled());
scanner.setResourceLoader(loader);
scanner.setEnvironment(environment);
@@ -93,4 +93,10 @@ public abstract class RepositoryConfigurationSourceSupport implements Repository
protected Iterable<TypeFilter> getIncludeFilters() {
return Collections.emptySet();
}
/**
* Controls whether nested repository-interfaces (e.g. defined as inner classes) should be considered by the
* repository infrastructure.
*/
public abstract boolean isConsideringNestedRepositoriesEnabled();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 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.
@@ -31,6 +31,7 @@ import org.w3c.dom.Element;
* XML based {@link RepositoryConfigurationSource}. Uses configuration defined on {@link Element} attributes.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSourceSupport {
@@ -39,6 +40,7 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
private static final String NAMED_QUERIES_LOCATION = "named-queries-location";
private static final String REPOSITORY_IMPL_POSTFIX = "repository-impl-postfix";
private static final String REPOSITORY_FACTORY_BEAN_CLASS_NAME = "factory-class";
private static final String CONSIDER_NESTED_REPOSITORIES = "consider-nested-repositories";
private final Element element;
private final ParserContext context;
@@ -148,4 +150,14 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
String attribute = element.getAttribute(attributeName);
return StringUtils.hasText(attribute) ? attribute : null;
}
/* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#isConsideringNestedRepositoriesEnabled()
*/
@Override
public boolean isConsideringNestedRepositoriesEnabled() {
String attribute = getNullDefaultedAttribute(element, CONSIDER_NESTED_REPOSITORIES);
return attribute != null && Boolean.parseBoolean(attribute);
}
}

View File

@@ -2,4 +2,5 @@ http\://www.springframework.org/schema/data/repository/spring-repository-1.0.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-1.4.xsd=org/springframework/data/repository/config/spring-repository-1.4.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd=org/springframework/data/repository/config/spring-repository-1.5.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd=org/springframework/data/repository/config/spring-repository-1.6.xsd
http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.6.xsd
http\://www.springframework.org/schema/data/repository/spring-repository-1.7.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd
http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd

View File

@@ -0,0 +1,256 @@
<?xml version="1.0" encoding="UTF-8" ?>
<xsd:schema xmlns="http://www.springframework.org/schema/data/repository"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:context="http://www.springframework.org/schema/context"
targetNamespace="http://www.springframework.org/schema/data/repository"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:import namespace="http://www.springframework.org/schema/context"
schemaLocation="http://www.springframework.org/schema/context/spring-context.xsd" />
<xsd:complexType name="repositories">
<xsd:sequence>
<xsd:element name="include-filter" type="context:filterType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls which eligible types to include for component scanning.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="exclude-filter" type="context:filterType" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls which eligible types to exclude for component scanning.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="base-package" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the base package where the DAO interface will be tried to be detected.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="named-queries-location" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the location to look for a Properties file containing externally defined queries.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="consider-nested-repositories" type="xsd:boolean" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Controls whether nested repository interface definitions should be considered.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="populator">
<xsd:attribute name="locations" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
Where to find the files to read the objects from the repository shall be populated with.
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="id" type="xsd:string" />
</xsd:complexType>
<!-- XML (Unmarshaller) initializer -->
<xsd:element name="unmarshaller-populator">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="populator">
<xsd:attribute name="unmarshaller-ref" type="unmarshallerRefType" use="required" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="unmarshallerRefType">
<xsd:annotation>
<xsd:appinfo>
<tool:expected-type type="org.springframework.oxm.Unmarshaller" />
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<!-- JSON (Jackson) initializer -->
<xsd:element name="jackson-populator">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="populator">
<xsd:attribute name="object-mapper-ref" type="objectMapperType" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="objectMapperType">
<xsd:annotation>
<xsd:appinfo>
<tool:expected-type type="org.codehaus.jackson.map.ObjectMapper" />
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<!-- JSON (Jackson2) initializer -->
<xsd:element name="jackson2-populator">
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="populator">
<xsd:attribute name="object-mapper-ref" type="objectMapper2Type" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:simpleType name="objectMapper2Type">
<xsd:annotation>
<xsd:appinfo>
<tool:expected-type type="com.fasterxml.jackson.databind.ObjectMapper" />
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string" />
</xsd:simpleType>
<xsd:attributeGroup name="repository-attributes">
<xsd:attribute name="repository-impl-postfix" type="xsd:string"/>
<xsd:attribute name="query-lookup-strategy" type="query-strategy"/>
<xsd:attribute name="factory-class" type="classType"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="transactional-repository-attributes">
<xsd:attributeGroup ref="repository-attributes"/>
<xsd:attribute name="transaction-manager-ref" type="transactionManagerRef"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="auditing-attributes">
<xsd:attribute name="auditor-aware-ref">
<xsd:annotation>
<xsd:documentation><![CDATA[
References a bean of type AuditorAware to represent the current principal.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.domain.AuditorAware" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="set-dates" default="true" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures whether the creation and modification dates are set.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="date-time-provider-ref">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures a DateTimeProvider that allows customizing which DateTime shall be used for setting
creation and modification dates.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.data.jpa.domain.support.DateTimeProvider" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="modify-on-creation" default="true" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Configures whether the entity shall be marked as modified on creation.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:simpleType name="query-strategy">
<xsd:annotation>
<xsd:documentation><![CDATA[
Determines the way query methods are being executed.
]]></xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="create-if-not-found">
<xsd:annotation>
<xsd:documentation><![CDATA[
Tries to find a named query but creates a custom query if
none can be found. (Default)
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="create">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a query from the query method's name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="use-declared-query">
<xsd:annotation>
<xsd:documentation><![CDATA[
Uses a declared query to execute. Fails if no
declared query (either through named query or through @Query)
is defined.
]]></xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="customImplementationReference">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref"/>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="entityManagerFactoryRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.orm.jpa.AbstractEntityManagerFactoryBean"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="transactionManagerRef">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:assignable-to type="org.springframework.transaction.PlatformTransactionManager"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="classType">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="direct">
<tool:expected-type type="java.lang.Class"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:union memberTypes="xsd:string"/>
</xsd:simpleType>
</xsd:schema>

View File

@@ -32,6 +32,7 @@ import org.springframework.core.type.StandardAnnotationMetadata;
* Unit tests for {@link AnnotationRepositoryConfigurationSource}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class AnnotationRepositoryConfigurationSourceUnitTests {
@@ -70,6 +71,7 @@ public class AnnotationRepositoryConfigurationSourceUnitTests {
Iterable<String> packages = source.getBasePackages();
assertThat(packages, hasItem(DefaultConfiguration.class.getPackage().getName()));
assertThat(source.isConsideringNestedRepositoriesEnabled(), is(false));
}
@Test
@@ -83,17 +85,27 @@ public class AnnotationRepositoryConfigurationSourceUnitTests {
assertThat(packages, hasItem("foo"));
}
public static class Person {
/**
* @see DATACMNS-90
*/
@Test
public void returnsConsiderNestedRepositories() {
AnnotationMetadata metadata = new StandardAnnotationMetadata(DefaultConfigurationWithNestedRepositories.class);
RepositoryConfigurationSource source = new AnnotationRepositoryConfigurationSource(metadata,
EnableRepositories.class, environment);
assertThat(source.isConsideringNestedRepositoriesEnabled(), is(true));
}
public static class Person {}
@EnableRepositories
static class DefaultConfiguration {
}
static class DefaultConfiguration {}
@EnableRepositories(basePackages = "foo")
static class DefaultConfigurationWithBasePackage {
static class DefaultConfigurationWithBasePackage {}
}
@EnableRepositories(considerNestedRepositories = true)
static class DefaultConfigurationWithNestedRepositories {}
}

View File

@@ -44,4 +44,6 @@ public @interface EnableRepositories {
String namedQueriesLocation() default "";
String repositoryImplementationPostfix() default "Impl";
boolean considerNestedRepositories() default false;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012 the original author or authors.
* Copyright 2012-2013 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.
@@ -23,16 +23,20 @@ import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSourceUnitTests.Person;
import org.springframework.data.repository.sample.SampleAnnotatedRepository;
/**
* Unit tests for {@link RepositoryComponentProvider}.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
public class RepositoryComponentProviderUnitTests {
@@ -57,4 +61,23 @@ public class RepositoryComponentProviderUnitTests {
assertThat(components.size(), is(1));
assertThat(components.iterator().next().getBeanClassName(), is(MyOtherRepository.class.getName()));
}
/**
* @DATACMNS-90
*/
@Test
public void shouldConsiderNestedRepositoryInterfacesIfEnabled() {
RepositoryComponentProvider provider = new RepositoryComponentProvider(Collections.<TypeFilter> emptyList());
provider.setConsiderNestedRepositoryInterfaces(true);
Set<BeanDefinition> components = provider.findCandidateComponents("org.springframework.data.repository.config");
String nestedRepositoryClassName = "org.springframework.data.repository.config.RepositoryComponentProviderUnitTests$MyNestedRepository";
assertThat(components.size(), is(3));
assertThat(components,
Matchers.<BeanDefinition> hasItem(hasProperty("beanClassName", is(nestedRepositoryClassName))));
}
public interface MyNestedRepository extends Repository<Person, Long> {}
}

View File

@@ -33,7 +33,7 @@ import org.springframework.oxm.jaxb.Jaxb2Marshaller;
import org.springframework.test.util.ReflectionTestUtils;
/**
* Integratin tests for the initializer namespace elements.
* Integration tests for the initializer name-space elements.
*
* @author Oliver Gierke
*/