diff --git a/pom.xml b/pom.xml index 0cd3b24018..45c9f26589 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 2.0.0.BUILD-SNAPSHOT + 2.0.0.DATACMNS-1172-SNAPSHOT Spring Data Core diff --git a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java index 1db8502280..618fa13382 100644 --- a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -53,6 +53,7 @@ * @author Thomas Darimont * @author Peter Rietzler * @author Jens Schauder + * @author Mark Paluch */ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigurationSourceSupport { @@ -64,6 +65,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass"; private static final String REPOSITORY_BASE_CLASS = "repositoryBaseClass"; private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories"; + private static final String LIMIT_IMPLEMENTATION_BASE_PACKAGES = "limitImplementationBasePackages"; private final AnnotationMetadata configMetadata; private final AnnotationMetadata enableAnnotationMetadata; @@ -320,6 +322,20 @@ public boolean shouldConsiderNestedRepositories() { return attributes.containsKey(CONSIDER_NESTED_REPOSITORIES) && attributes.getBoolean(CONSIDER_NESTED_REPOSITORIES); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSourceSupport#shouldLimitRepositoryImplementationBasePackages() + */ + @Override + public boolean shouldLimitRepositoryImplementationBasePackages() { + + if (!attributes.containsKey(LIMIT_IMPLEMENTATION_BASE_PACKAGES)) { + return true; + } + + return attributes.getBoolean(LIMIT_IMPLEMENTATION_BASE_PACKAGES); + } + /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfigurationSource#getAttribute(java.lang.String) diff --git a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java index 593c110b76..3552ab2fff 100644 --- a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java +++ b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java @@ -46,6 +46,7 @@ * @author Christoph Strobl * @author Peter Rietzler * @author Jens Schauder + * @author Mark Paluch */ @RequiredArgsConstructor public class CustomRepositoryImplementationDetector { @@ -71,7 +72,7 @@ public Optional detectCustomImplementation(RepositoryCon return detectCustomImplementation( // configuration.getImplementationClassName(), // configuration.getImplementationBeanName(), // - configuration.getBasePackages(), // + configuration.getImplementationBasePackages(configuration.getImplementationClassName()), // configuration.getExcludeFilters(), // bd -> configuration.getConfigurationSource().generateBeanName(bd)); } diff --git a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java index 7bb5777497..98180ddc43 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java @@ -34,6 +34,7 @@ * * @author Oliver Gierke * @author Jens Schauder + * @author Mark Paluch */ @RequiredArgsConstructor public class DefaultRepositoryConfiguration @@ -71,6 +72,18 @@ public Streamable getBasePackages() { return configurationSource.getBasePackages(); } + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfiguration#getBasePackages(String) + */ + @Override + public Streamable getImplementationBasePackages(String interfaceClassName) { + + return configurationSource.shouldLimitRepositoryImplementationBasePackages() + ? Streamable.of(ClassUtils.getPackageName(interfaceClassName)) + : getBasePackages(); + } + /* * (non-Javadoc) * @see org.springframework.data.repository.config.RepositoryConfiguration#getRepositoryInterface() diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java index f351b87074..7d5b81c81a 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java @@ -202,7 +202,8 @@ private Optional detectRepositoryFragmentConfig .concat(configuration.getConfigurationSource().getRepositoryImplementationPostfix().orElse("Impl")); Optional beanDefinition = implementationDetector.detectCustomImplementation(className, null, - configuration.getBasePackages(), exclusions, bd -> configuration.getConfigurationSource().generateBeanName(bd)); + configuration.getImplementationBasePackages(fragmentInterfaceName), exclusions, + bd -> configuration.getConfigurationSource().generateBeanName(bd)); return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(fragmentInterfaceName, bd)); } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java index ccd4183155..b1dc5368b2 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java @@ -37,6 +37,15 @@ public interface RepositoryConfiguration getBasePackages(); + /** + * Returns the base packages to scan for repository implementations. + * + * @param interfaceClassName class name of the interface. + * @return + * @since 2.0 + */ + Streamable getImplementationBasePackages(String interfaceClassName); + /** * Returns the interface name of the repository. * diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java index 3e4113e948..d48170f9cb 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java @@ -31,6 +31,7 @@ * @author Thomas Darimont * @author Peter Rietzler * @author Jens Schauder + * @author Mark Paluch */ public interface RepositoryConfigurationSource { @@ -62,6 +63,17 @@ public interface RepositoryConfigurationSource { */ Optional getRepositoryImplementationPostfix(); + /** + * Returns whether to limit repository implementation base packages for custom implementation scanning. If + * {@literal true}, then custom implementation scanning considers only the package of the repository/fragment + * interface and its subpackages for a scan. Otherwise, all {@link #getBasePackages()} are scanned for repository + * implementations + * + * @return {@literal true} if base packages are limited to the actual repository package. + * @since 2.0 + */ + boolean shouldLimitRepositoryImplementationBasePackages(); + /** * @return */ diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java index 2014a75589..3851a9cbc1 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSourceSupport.java @@ -117,4 +117,13 @@ protected Iterable getIncludeFilters() { public boolean shouldConsiderNestedRepositories() { return false; } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryConfigurationSource#isLimitRepositoryImplementationBasePackages() + */ + @Override + public boolean shouldLimitRepositoryImplementationBasePackages() { + return true; + } } diff --git a/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java b/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java index a15e184ed4..fbb5bfaec6 100755 --- a/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSourceUnitTests.java @@ -73,8 +73,9 @@ public void evaluatesExcludeFiltersCorrectly() { Streamable candidates = source.getCandidates(new DefaultResourceLoader()); - assertThat(candidates).hasSize(2).extracting("beanClassName").containsOnly(MyRepository.class.getName(), - ComposedRepository.class.getName()); + assertThat(candidates).extracting("beanClassName") + .contains(MyRepository.class.getName(), ComposedRepository.class.getName()) + .doesNotContain(MyOtherRepository.class.getName(), ExcludedRepository.class.getName()); } @Test // DATACMNS-47 @@ -102,6 +103,14 @@ public void returnsConsiderNestedRepositories() { assertThat(source.shouldConsiderNestedRepositories()).isTrue(); } + @Test // DATACMNS-1172 + public void returnsLimitImplementationBasePackages() { + + assertThat(getConfigSource(DefaultConfiguration.class).shouldLimitRepositoryImplementationBasePackages()).isTrue(); + assertThat(getConfigSource(DefaultConfigurationWithoutBasePackageLimit.class) + .shouldLimitRepositoryImplementationBasePackages()).isFalse(); + } + @Test // DATACMNS-456 public void findsStringAttributeByName() { @@ -155,6 +164,9 @@ static class DefaultConfigurationWithBasePackage {} @EnableRepositories(considerNestedRepositories = true) static class DefaultConfigurationWithNestedRepositories {} + @EnableRepositories(limitImplementationBasePackages = false) + static class DefaultConfigurationWithoutBasePackageLimit {} + @EnableRepositories(excludeFilters = { @Filter(Primary.class) }) static class ConfigurationWithExplicitFilter {} diff --git a/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java b/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java index 42253d1ea2..e71f66c483 100755 --- a/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java @@ -34,19 +34,20 @@ import org.springframework.beans.factory.config.ConstructorArgumentValues; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.data.util.Streamable; /** * Unit tests for {@link DefaultRepositoryConfiguration}. * * @author Oliver Gierke * @author Jens Schauder + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class DefaultRepositoryConfigurationUnitTests { @Mock RepositoryConfigurationSource source; - BeanDefinition definition = new RootBeanDefinition("com.acme.MyRepository"); RepositoryConfigurationExtension extension = new SimplerRepositoryConfigurationExtension("factory", "module"); @Before @@ -83,6 +84,33 @@ public void prefersSourcesRepositoryFactoryBeanClass() { assertThat(getConfiguration(source).getRepositoryFactoryBeanClassName()).isEqualTo("custom"); } + @Test // DATACMNS-1172 + public void limitsImplementationBasePackages() { + + when(source.shouldLimitRepositoryImplementationBasePackages()).thenReturn(true); + + assertThat(getConfiguration(source).getImplementationBasePackages("com.acme.MyRepository")) + .containsOnly("com.acme"); + } + + @Test // DATACMNS-1172 + public void limitsImplementationBasePackagesOfNestedClass() { + + when(source.shouldLimitRepositoryImplementationBasePackages()).thenReturn(true); + + assertThat(getConfiguration(source).getImplementationBasePackages(NestedInterface.class.getName())) + .containsOnly("org.springframework.data.repository.config"); + } + + @Test // DATACMNS-1172 + public void shouldNotLimitImplementationBasePackages() { + + when(source.getBasePackages()).thenReturn(Streamable.of("com", "org.coyote")); + + assertThat(getConfiguration(source).getImplementationBasePackages("com.acme.MyRepository")).contains("com", + "org.coyote"); + } + private DefaultRepositoryConfiguration getConfiguration( RepositoryConfigurationSource source) { RootBeanDefinition beanDefinition = createBeanDefinition(); @@ -105,4 +133,6 @@ private static RootBeanDefinition createBeanDefinition() { return beanDefinition; } + + private interface NestedInterface {} } diff --git a/src/test/java/org/springframework/data/repository/config/EnableRepositories.java b/src/test/java/org/springframework/data/repository/config/EnableRepositories.java index 41798ad26b..d6991138c6 100644 --- a/src/test/java/org/springframework/data/repository/config/EnableRepositories.java +++ b/src/test/java/org/springframework/data/repository/config/EnableRepositories.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 the original author or authors. + * Copyright 2012-2017 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. @@ -49,4 +49,6 @@ String repositoryImplementationPostfix() default "Impl"; boolean considerNestedRepositories() default false; + + boolean limitImplementationBasePackages() default true; } diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java index 5add796e46..f8d37edcd4 100755 --- a/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryBeanDefinitionRegistrarSupportUnitTests.java @@ -34,6 +34,7 @@ import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.data.repository.config.basepackage.FragmentImpl; import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean; /** @@ -84,6 +85,28 @@ public void registersBeanDefinitionWithoutFragmentImplementations() { assertNoBeanDefinitionRegisteredFor("excludedRepositoryImpl"); } + @Test // DATACMNS-1172 + public void shouldLimitImplementationBasePackages() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(LimitsImplementationBasePackages.class, true); + + registrar.registerBeanDefinitions(metadata, registry); + + assertBeanDefinitionRegisteredFor("personRepository"); + assertNoBeanDefinitionRegisteredFor("fragmentImpl"); + } + + @Test // DATACMNS-1172 + public void shouldNotLimitImplementationBasePackages() { + + AnnotationMetadata metadata = new StandardAnnotationMetadata(UnlimitedImplementationBasePackages.class, true); + + registrar.registerBeanDefinitions(metadata, registry); + + assertBeanDefinitionRegisteredFor("personRepository"); + assertBeanDefinitionRegisteredFor("fragmentImpl"); + } + @Test // DATACMNS-360 public void registeredProfileRepositoriesIfProfileActivated() { @@ -152,7 +175,11 @@ protected String getModulePrefix() { @EnableRepositories( includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = RepositoryWithFragmentExclusion.class), basePackageClasses = RepositoryWithFragmentExclusion.class) - static class FragmentExclusionConfiguration { + static class FragmentExclusionConfiguration {} - } + @EnableRepositories(basePackageClasses = FragmentImpl.class) + static class LimitsImplementationBasePackages {} + + @EnableRepositories(basePackageClasses = FragmentImpl.class, limitImplementationBasePackages = false) + static class UnlimitedImplementationBasePackages {} } diff --git a/src/test/java/org/springframework/data/repository/config/basepackage/FragmentImpl.java b/src/test/java/org/springframework/data/repository/config/basepackage/FragmentImpl.java new file mode 100644 index 0000000000..b1dfb3a4aa --- /dev/null +++ b/src/test/java/org/springframework/data/repository/config/basepackage/FragmentImpl.java @@ -0,0 +1,23 @@ +/* + * Copyright 2017 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.basepackage; + +import org.springframework.data.repository.config.basepackage.repo.Fragment; + +/** + * @author Mark Paluch + */ +public class FragmentImpl implements Fragment {} diff --git a/src/test/java/org/springframework/data/repository/config/basepackage/repo/Fragment.java b/src/test/java/org/springframework/data/repository/config/basepackage/repo/Fragment.java new file mode 100644 index 0000000000..d4a35d8612 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/config/basepackage/repo/Fragment.java @@ -0,0 +1,21 @@ +/* + * Copyright 2017 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.basepackage.repo; + +/** + * @author Mark Paluch + */ +public interface Fragment {} diff --git a/src/test/java/org/springframework/data/repository/config/basepackage/repo/PersonRepository.java b/src/test/java/org/springframework/data/repository/config/basepackage/repo/PersonRepository.java new file mode 100644 index 0000000000..f94996761b --- /dev/null +++ b/src/test/java/org/springframework/data/repository/config/basepackage/repo/PersonRepository.java @@ -0,0 +1,24 @@ +/* + * Copyright 2017 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.basepackage.repo; + +import org.springframework.data.mapping.Person; +import org.springframework.data.repository.Repository; + +/** + * @author Mark Paluch + */ +public interface PersonRepository extends Repository, Fragment {}