DATACMNS-47 - Add support for JavaConfig based repository configuration.

Completely rewrote namespace bean definition parsing to be extendable more easily. Separated XML concerns from annotation based configuration. The central point to extend the namespace parsing for a certain module is now hidden behind the RepositoryConfigurationExtension interface (have a look at the RepositoryConfigurationExtensionSupport base class as well).
This commit is contained in:
Oliver Gierke
2012-07-05 17:16:03 +02:00
parent 464c3f048b
commit 96795f2980
36 changed files with 2176 additions and 1471 deletions

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2012 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 static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Collection;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
/**
* Unit tests for {@link AnnotationRepositoryConfigurationSource}.
*
* @author Oliver Gierke
*/
public class AnnotationRepositoryConfigurationSourceUnitTests {
RepositoryConfigurationSource source;
@Before
public void setUp() {
AnnotationMetadata annotationMetadata = new StandardAnnotationMetadata(SampleConfiguration.class, true);
source = new AnnotationRepositoryConfigurationSource(annotationMetadata, EnableRepositories.class);
}
@Test
public void findsBasePackagesForClasses() {
Iterable<String> basePackages = source.getBasePackages();
assertThat(basePackages, hasItem(AnnotationRepositoryConfigurationSourceUnitTests.class.getPackage().getName()));
}
@Test
public void evaluatesExcludeFiltersCorrectly() {
Collection<String> candidates = source.getCandidates(new DefaultResourceLoader());
assertThat(candidates, hasSize(1));
assertThat(candidates, hasItem(MyRepository.class.getName()));
}
@Test
public void defaultsToPackageOfAnnotatedClass() {
AnnotationMetadata metadata = new StandardAnnotationMetadata(DefaultConfiguration.class);
RepositoryConfigurationSource source = new AnnotationRepositoryConfigurationSource(metadata,
EnableRepositories.class);
Iterable<String> packages = source.getBasePackages();
assertThat(packages, hasItem(DefaultConfiguration.class.getPackage().getName()));
}
@Test
public void returnsConfiguredBasePackage() {
AnnotationMetadata metadata = new StandardAnnotationMetadata(DefaultConfigurationWithBasePackage.class);
RepositoryConfigurationSource source = new AnnotationRepositoryConfigurationSource(metadata,
EnableRepositories.class);
Iterable<String> packages = source.getBasePackages();
assertThat(packages, hasItem("foo"));
}
public static class Person {
}
@EnableRepositories
static class DefaultConfiguration {
}
@EnableRepositories(basePackages = "foo")
static class DefaultConfigurationWithBasePackage {
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright 2012 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 static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
/**
* Unit tests for {@link DefaultRepositoryConfiguration}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultRepositoryConfigurationUnitTests {
@Mock
RepositoryConfigurationSource source;
@Test
public void supportsBasicConfiguration() {
RepositoryConfiguration<RepositoryConfigurationSource> configuration = new DefaultRepositoryConfiguration<RepositoryConfigurationSource>(
source, "com.acme.MyRepository");
assertThat(configuration.getBeanId(), is("myRepository"));
assertThat(configuration.getConfigurationSource(), is(source));
assertThat(configuration.getImplementationBeanName(), is("myRepositoryImpl"));
assertThat(configuration.getImplementationClassName(), is("MyRepositoryImpl"));
assertThat(configuration.getRepositoryInterface(), is("com.acme.MyRepository"));
assertThat(configuration.getQueryLookupStrategyKey(), is((Object) Key.CREATE_IF_NOT_FOUND));
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2012 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.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
@Retention(RetentionPolicy.RUNTIME)
public @interface EnableRepositories {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
Class<?> repositoryFactoryBeanClass() default RepositoryFactoryBeanSupport.class;
String namedQueriesLocation() default "";
String repositoryImplementationPostfix() default "Impl";
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2012 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 org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSourceUnitTests.Person;
interface MyOtherRepository extends Repository<Person, Long> {
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright 2012 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 org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSourceUnitTests.Person;
interface MyRepository extends Repository<Person, Long> {
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright 2012 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 static org.mockito.Matchers.*;
import static org.mockito.Mockito.*;
import java.lang.annotation.Annotation;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
/**
* Integration test for {@link RepositoryBeanDefinitionRegistrarSupport}.
*
* @author Oliver Gierke
*/
@RunWith(MockitoJUnitRunner.class)
public class RepositoryBeanDefinitionRegistrarSupportIntegrationTests {
@Mock
BeanDefinitionRegistry registry;
@Test
public void registersBeanDefinitionForFoundBean() {
AnnotationMetadata metadata = new StandardAnnotationMetadata(SampleConfiguration.class, true);
DummyRegistrar registrar = new DummyRegistrar();
registrar.registerBeanDefinitions(metadata, registry);
verify(registry, times(1)).registerBeanDefinition(eq("myRepository"), any(BeanDefinition.class));
}
private static class DummyRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
*/
@Override
protected Class<? extends Annotation> getAnnotation() {
return EnableRepositories.class;
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
*/
@Override
protected RepositoryConfigurationExtension getExtension() {
return new RepositoryConfigurationExtensionSupport() {
public String getRepositoryFactoryClassName() {
return RepositoryFactoryBeanSupport.class.getName();
}
@Override
protected String getModulePrefix() {
return "commons";
}
};
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2011 the original author or authors.
* Copyright 2012 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.
@@ -18,12 +18,15 @@ package org.springframework.data.repository.config;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.config.AbstractRepositoryConfigDefinitionParser.RepositoryComponentProvider;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.repository.sample.SampleAnnotatedRepository;
/**
@@ -36,10 +39,22 @@ public class RepositoryComponentProviderUnitTests {
@Test
public void findsAnnotatedRepositoryInterface() {
RepositoryComponentProvider provider = new RepositoryComponentProvider(Repository.class);
RepositoryComponentProvider provider = new RepositoryComponentProvider(Collections.<TypeFilter> emptyList());
Set<BeanDefinition> components = provider.findCandidateComponents("org.springframework.data.repository.sample");
assertThat(components.size(), is(1));
assertThat(components.iterator().next().getBeanClassName(), is(SampleAnnotatedRepository.class.getName()));
}
@Test
public void limitsFoundRepositoriesToIncludeFiltersOnly() {
List<? extends TypeFilter> filters = Arrays.asList(new AssignableTypeFilter(MyOtherRepository.class));
RepositoryComponentProvider provider = new RepositoryComponentProvider(filters);
Set<BeanDefinition> components = provider.findCandidateComponents("org.springframework.data.repository");
assertThat(components.size(), is(1));
assertThat(components.iterator().next().getBeanClassName(), is(MyOtherRepository.class.getName()));
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright 2012 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 org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FilterType;
@EnableRepositories(excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = MyOtherRepository.class), basePackageClasses = AnnotationRepositoryConfigurationSourceUnitTests.class)
class SampleConfiguration {
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2008-2010 the original author or authors.
* Copyright 2010-2012 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.
@@ -15,13 +15,16 @@
*/
package org.springframework.data.repository.config;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +35,8 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.repository.config.TypeFilterParser.Type;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
@@ -44,22 +49,24 @@ import org.xml.sax.SAXException;
@RunWith(MockitoJUnitRunner.class)
public class TypeFilterParserUnitTests {
private TypeFilterParser parser;
private Element documentElement;
static final Matcher<Iterable<? super AssignableTypeFilter>> IS_ASSIGNABLE_TYPE_FILTER = hasItem(Matchers
.isA(AssignableTypeFilter.class));
TypeFilterParser parser;
Element documentElement;
@Mock
private ClassLoader classLoader;
ReaderContext context;
@Mock
ClassLoader classLoader;
@Mock
private ReaderContext context;
@Mock
private ClassPathScanningCandidateComponentProvider scanner;
ClassPathScanningCandidateComponentProvider scanner;
@Before
public void setUp() throws SAXException, IOException, ParserConfigurationException {
parser = new TypeFilterParser(classLoader, context);
parser = new TypeFilterParser(context, classLoader);
Resource sampleXmlFile = new ClassPathResource("type-filter-test.xml", TypeFilterParserUnitTests.class);
@@ -74,9 +81,8 @@ public class TypeFilterParserUnitTests {
Element element = DomUtils.getChildElementByTagName(documentElement, "firstSample");
parser.parseFilters(element, scanner);
verify(scanner, atLeastOnce()).addIncludeFilter(isA(AssignableTypeFilter.class));
Iterable<TypeFilter> filters = parser.parseTypeFilters(element, Type.INCLUDE);
assertThat(filters, IS_ASSIGNABLE_TYPE_FILTER);
}
@Test
@@ -84,8 +90,7 @@ public class TypeFilterParserUnitTests {
Element element = DomUtils.getChildElementByTagName(documentElement, "secondSample");
parser.parseFilters(element, scanner);
verify(scanner, atLeastOnce()).addExcludeFilter(isA(AssignableTypeFilter.class));
Iterable<TypeFilter> filters = parser.parseTypeFilters(element, Type.EXCLUDE);
assertThat(filters, IS_ASSIGNABLE_TYPE_FILTER);
}
}