From b43258086ccbe208d1b27ec06877f68c8c6bb12e Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 6 Feb 2018 16:06:55 +0100 Subject: [PATCH 1/5] DATACMNS-1255 - Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 6ca40eba5c..cf5c529ce8 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 2.1.0.BUILD-SNAPSHOT + 2.1.0.DATACMNS-1255-SNAPSHOT Spring Data Core From d55acacdf58e8df9d0d860ff4683688444f90754 Mon Sep 17 00:00:00 2001 From: Fabian Henniges Date: Sun, 4 Feb 2018 12:38:07 +0100 Subject: [PATCH 2/5] DATACMNS-1255 - Allow configuration of QueryLookupStrategy for CDI repositories. --- .../data/repository/cdi/CdiRepositoryBean.java | 7 +++++++ .../repository/cdi/CdiRepositoryConfiguration.java | 11 +++++++++++ .../config/DefaultRepositoryConfiguration.java | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java index 9342d144b8..10f1b777bc 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java @@ -44,6 +44,7 @@ import org.springframework.data.repository.config.DefaultRepositoryConfiguration; import org.springframework.data.repository.config.RepositoryBeanNameGenerator; import org.springframework.data.repository.config.SpringDataAnnotationBeanNameGenerator; +import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -429,5 +430,11 @@ static enum DefaultCdiRepositoryConfiguration implements CdiRepositoryConfigurat public String getRepositoryImplementationPostfix() { return DefaultRepositoryConfiguration.DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX; } + + @Override + public QueryLookupStrategy.Key getQueryLookupStrategy() { + return DefaultRepositoryConfiguration.DEFAULT_QUERY_LOOKUP_STRATEGY; + } + } } diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java index 07d913f236..1fff25a5ab 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.data.repository.cdi; +import org.springframework.data.repository.query.QueryLookupStrategy; + /** * Interface containing the configurable options for the Spring Data repository subsystem using CDI. * @@ -29,4 +31,13 @@ public interface CdiRepositoryConfiguration { * @return the postfix to use, must not be {@literal null}. */ String getRepositoryImplementationPostfix(); + + + /** + * Return the strategy to lookup queries + * + * @return the lookup strategy to use + */ + QueryLookupStrategy.Key getQueryLookupStrategy(); + } 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 aca90c3694..e69ca17892 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java @@ -41,7 +41,7 @@ public class DefaultRepositoryConfiguration { public static final String DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX = "Impl"; - private static final Key DEFAULT_QUERY_LOOKUP_STRATEGY = Key.CREATE_IF_NOT_FOUND; + public static final Key DEFAULT_QUERY_LOOKUP_STRATEGY = Key.CREATE_IF_NOT_FOUND; private final @NonNull T configurationSource; private final @NonNull BeanDefinition definition; From c001878599cf512e4082a2c985080de20202d9eb Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 5 Feb 2018 16:54:19 +0100 Subject: [PATCH 3/5] DATACMNS-1255 - Polishing. Add default QueryLookupStrategy key lookup method to CdiRepositoryConfiguration to not break existing modules by forcing these to implement a new method. --- .../data/repository/cdi/CdiRepositoryBean.java | 7 ------- .../repository/cdi/CdiRepositoryConfiguration.java | 13 ++++++++----- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java index 10f1b777bc..9342d144b8 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java @@ -44,7 +44,6 @@ import org.springframework.data.repository.config.DefaultRepositoryConfiguration; import org.springframework.data.repository.config.RepositoryBeanNameGenerator; import org.springframework.data.repository.config.SpringDataAnnotationBeanNameGenerator; -import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -430,11 +429,5 @@ static enum DefaultCdiRepositoryConfiguration implements CdiRepositoryConfigurat public String getRepositoryImplementationPostfix() { return DefaultRepositoryConfiguration.DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX; } - - @Override - public QueryLookupStrategy.Key getQueryLookupStrategy() { - return DefaultRepositoryConfiguration.DEFAULT_QUERY_LOOKUP_STRATEGY; - } - } } diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java index 1fff25a5ab..30ba100659 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java @@ -16,12 +16,14 @@ package org.springframework.data.repository.cdi; +import org.springframework.data.repository.config.DefaultRepositoryConfiguration; import org.springframework.data.repository.query.QueryLookupStrategy; /** * Interface containing the configurable options for the Spring Data repository subsystem using CDI. * * @author Mark Paluch + * @author Fabian Henniges */ public interface CdiRepositoryConfiguration { @@ -32,12 +34,13 @@ public interface CdiRepositoryConfiguration { */ String getRepositoryImplementationPostfix(); - /** - * Return the strategy to lookup queries + * Return the strategy to lookup queries. * - * @return the lookup strategy to use + * @return the lookup strategy to use. + * @since 2.1 */ - QueryLookupStrategy.Key getQueryLookupStrategy(); - + default QueryLookupStrategy.Key getQueryLookupStrategy() { + return DefaultRepositoryConfiguration.DEFAULT_QUERY_LOOKUP_STRATEGY; + } } From afc39d302c50ad2561a454b6f2c09b8f9c64c61d Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Tue, 6 Feb 2018 16:06:29 +0100 Subject: [PATCH 4/5] DATACMNS-1255 - Extend configuration of CDI repository settings. We now extended CDI repository configuration to allow configuration of EvaluationContextProvider, NamedQueries, QueryLookupStrategy keys and the repository base class. CdiRepositoryConfiguration is now an interface with default-methods only providing default configuration values. --- .../repository/cdi/CdiRepositoryBean.java | 42 ++++++++++---- .../cdi/CdiRepositoryConfiguration.java | 52 ++++++++++++++--- .../cdi/CdiRepositoryBeanUnitTests.java | 56 ++++++++++++++++++- 3 files changed, 128 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java index 9342d144b8..7204ce7d29 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java @@ -41,9 +41,9 @@ import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; -import org.springframework.data.repository.config.DefaultRepositoryConfiguration; import org.springframework.data.repository.config.RepositoryBeanNameGenerator; import org.springframework.data.repository.config.SpringDataAnnotationBeanNameGenerator; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -407,6 +407,34 @@ protected T create(CreationalContext creationalContext, Class repositoryTy + "in order to use custom repository implementations"); } + /** + * Applies the configuration from {@link CdiRepositoryConfiguration} to {@link RepositoryFactorySupport} by looking up + * the actual configuration. + * + * @param repositoryFactory will never be {@literal null}. + * @since 2.1 + */ + protected void applyConfiguration(RepositoryFactorySupport repositoryFactory) { + applyConfiguration(repositoryFactory, lookupConfiguration(beanManager, qualifiers)); + } + + /** + * Applies the configuration from {@link CdiRepositoryConfiguration} to {@link RepositoryFactorySupport} by looking up + * the actual configuration. + * + * @param repositoryFactory will never be {@literal null}. + * @param configuration will never be {@literal null}. + * @since 2.1 + */ + protected static void applyConfiguration(RepositoryFactorySupport repositoryFactory, + CdiRepositoryConfiguration configuration) { + + configuration.getEvaluationContextProvider().ifPresent(repositoryFactory::setEvaluationContextProvider); + configuration.getNamedQueries().ifPresent(repositoryFactory::setNamedQueries); + configuration.getQueryLookupStrategy().ifPresent(repositoryFactory::setQueryLookupStrategyKey); + configuration.getRepositoryBeanClass().ifPresent(repositoryFactory::setRepositoryBaseClass); + } + /* * (non-Javadoc) * @see java.lang.Object#toString() @@ -417,17 +445,7 @@ public String toString() { qualifiers.toString()); } - static enum DefaultCdiRepositoryConfiguration implements CdiRepositoryConfiguration { - + enum DefaultCdiRepositoryConfiguration implements CdiRepositoryConfiguration { INSTANCE; - - /* - * (non-Javadoc) - * @see org.springframework.data.repository.cdi.CdiRepositoryConfiguration#getRepositoryImplementationPostfix() - */ - @Override - public String getRepositoryImplementationPostfix() { - return DefaultRepositoryConfiguration.DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX; - } } } diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java index 30ba100659..62360ddfc5 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfiguration.java @@ -16,7 +16,10 @@ package org.springframework.data.repository.cdi; -import org.springframework.data.repository.config.DefaultRepositoryConfiguration; +import java.util.Optional; + +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.query.EvaluationContextProvider; import org.springframework.data.repository.query.QueryLookupStrategy; /** @@ -28,19 +31,52 @@ public interface CdiRepositoryConfiguration { /** - * Returns the configured postfix to be used for looking up custom implementation classes. + * Return the {@link EvaluationContextProvider} to use. Can be {@link Optional#empty()} . * - * @return the postfix to use, must not be {@literal null}. + * @return the optional {@link EvaluationContextProvider} base to use, can be {@link Optional#empty()}, must not be + * {@literal null}. + * @since 2.1 + */ + default Optional getEvaluationContextProvider() { + return Optional.empty(); + } + + /** + * Return the {@link NamedQueries} to use. Can be {@link Optional#empty()}. + * + * @return the optional named queries to use, can be {@link Optional#empty()}, must not be {@literal null}. + * @since 2.1 + */ + default Optional getNamedQueries() { + return Optional.empty(); + } + + /** + * Return the {@link QueryLookupStrategy.Key} to lookup queries. Can be {@link Optional#empty()}. + * + * @return the lookup strategy to use, can be {@link Optional#empty()}, must not be {@literal null}. + * @since 2.1 */ - String getRepositoryImplementationPostfix(); + default Optional getQueryLookupStrategy() { + return Optional.empty(); + } /** - * Return the strategy to lookup queries. + * Return the {@link Class repository base class} to use. Can be {@link Optional#empty()} . * - * @return the lookup strategy to use. + * @return the optional repository base to use, can be {@link Optional#empty()}, must not be {@literal null}. * @since 2.1 */ - default QueryLookupStrategy.Key getQueryLookupStrategy() { - return DefaultRepositoryConfiguration.DEFAULT_QUERY_LOOKUP_STRATEGY; + default Optional> getRepositoryBeanClass() { + return Optional.empty(); + } + + /** + * Returns the configured postfix to be used for looking up custom implementation classes. + * + * @return the postfix to use, must not be {@literal null}. + */ + default String getRepositoryImplementationPostfix() { + return "Impl"; } } diff --git a/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java b/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java index c2abd71156..f12411b55b 100755 --- a/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java +++ b/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java @@ -39,11 +39,18 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.data.repository.Repository; import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; +import org.springframework.data.repository.core.NamedQueries; +import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries; +import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.query.DefaultEvaluationContextProvider; +import org.springframework.data.repository.query.EvaluationContextProvider; +import org.springframework.data.repository.query.QueryLookupStrategy.Key; /** * Unit tests for {@link CdiRepositoryBean}. * * @author Oliver Gierke + * @author Mark Paluch */ @RunWith(MockitoJUnitRunner.class) public class CdiRepositoryBeanUnitTests { @@ -55,6 +62,7 @@ public class CdiRepositoryBeanUnitTests { .singleton((Annotation) new CdiRepositoryExtensionSupport.DefaultAnnotationLiteral()); @Mock BeanManager beanManager; + @Mock RepositoryFactorySupport repositoryFactory; @Test(expected = IllegalArgumentException.class) public void voidRejectsNullQualifiers() { @@ -154,6 +162,25 @@ protected SampleRepository create( // ); } + @Test // DATACMNS-1255 + public void appliesRepositoryConfiguration() { + + DummyCdiRepositoryBean bean = new DummyCdiRepositoryBean(NO_ANNOTATIONS, + SampleRepository.class, beanManager) { + @Override + protected CdiRepositoryConfiguration lookupConfiguration(BeanManager beanManager, Set qualifiers) { + return RepositoryConfiguration.INSTANCE; + } + }; + + bean.applyConfiguration(repositoryFactory); + + verify(repositoryFactory).setEvaluationContextProvider(DefaultEvaluationContextProvider.INSTANCE); + verify(repositoryFactory).setNamedQueries(PropertiesBasedNamedQueries.EMPTY); + verify(repositoryFactory).setRepositoryBaseClass(String.class); + verify(repositoryFactory).setQueryLookupStrategyKey(Key.CREATE); + } + static class DummyCdiRepositoryBean extends CdiRepositoryBean { DummyCdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager) { @@ -168,12 +195,37 @@ protected T create(CreationalContext creationalContext, Class repositoryTy } @Named("namedRepository") - static interface SampleRepository extends Repository { + interface SampleRepository extends Repository { } @StereotypeAnnotation - static interface StereotypedSampleRepository { + interface StereotypedSampleRepository { + + } + + enum RepositoryConfiguration implements CdiRepositoryConfiguration { + + INSTANCE; + @Override + public Optional getEvaluationContextProvider() { + return Optional.of(DefaultEvaluationContextProvider.INSTANCE); + } + + @Override + public Optional getNamedQueries() { + return Optional.of(PropertiesBasedNamedQueries.EMPTY); + } + + @Override + public Optional getQueryLookupStrategy() { + return Optional.of(Key.CREATE); + } + + @Override + public Optional> getRepositoryBeanClass() { + return Optional.of(String.class); + } } } From 4120ecaf1407141c7fd8f43dcb0c5b0e9922dc77 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 7 Feb 2018 14:36:21 +0100 Subject: [PATCH 5/5] DATACMNS-1233 - Allow CDI Repositories to be composed of an arbitrary number of implementation classes. We now support repository fragments for repositories exported through CDI. --- .../repository/cdi/CdiRepositoryBean.java | 251 ++++++++++-------- .../repository/cdi/CdiRepositoryContext.java | 217 +++++++++++++++ .../cdi/CdiRepositoryExtensionSupport.java | 30 +-- .../repository/config/FragmentMetadata.java | 107 ++++++++ .../RepositoryBeanDefinitionBuilder.java | 106 ++------ .../config/RepositoryFragmentDiscovery.java | 45 ++++ .../cdi/AnotherFragmentInterface.java | 26 ++ .../cdi/AnotherFragmentInterfaceImpl.java | 32 +++ .../repository/cdi/AnotherRepositoryImpl.java | 8 +- .../cdi/CdiConfigurationIntegrationTests.java | 58 ++++ .../cdi/CdiRepositoryBeanUnitTests.java | 4 +- .../repository/cdi/ComposedRepository.java | 31 +++ .../cdi/ComposedRepositoryCustom.java | 29 ++ .../cdi/ComposedRepositoryImpl.java | 32 +++ .../repository/cdi/DummyCdiExtension.java | 12 +- .../repository/cdi/FragmentInterface.java | 23 ++ .../repository/cdi/FragmentInterfaceImpl.java | 27 ++ .../RepositoryFragmentsIntegrationTests.java | 77 ++++++ .../cdi/isolated/FragmentInterface.java | 24 ++ .../cdi/isolated/FragmentInterfaceFoo.java | 27 ++ .../isolated/IsolatedComposedRepository.java | 25 ++ .../cdi/isolated/MyCdiConfiguration.java | 29 ++ 22 files changed, 1006 insertions(+), 214 deletions(-) create mode 100644 src/main/java/org/springframework/data/repository/cdi/CdiRepositoryContext.java create mode 100644 src/main/java/org/springframework/data/repository/config/FragmentMetadata.java create mode 100644 src/main/java/org/springframework/data/repository/config/RepositoryFragmentDiscovery.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterface.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterfaceImpl.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/CdiConfigurationIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/ComposedRepository.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryCustom.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryImpl.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/FragmentInterface.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/FragmentInterfaceImpl.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/RepositoryFragmentsIntegrationTests.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterface.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterfaceFoo.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/isolated/IsolatedComposedRepository.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/isolated/MyCdiConfiguration.java diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java index 7204ce7d29..109fbc2b7e 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java @@ -25,12 +25,12 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.inject.Alternative; import javax.enterprise.inject.Stereotype; -import javax.enterprise.inject.UnsatisfiedResolutionException; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.InjectionPoint; @@ -38,15 +38,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition; -import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; -import org.springframework.data.repository.config.RepositoryBeanNameGenerator; -import org.springframework.data.repository.config.SpringDataAnnotationBeanNameGenerator; +import org.springframework.data.repository.config.RepositoryFragmentConfiguration; +import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; import org.springframework.data.repository.core.support.RepositoryFactorySupport; +import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.lang.Nullable; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -66,16 +64,12 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl private final Set qualifiers; private final Class repositoryType; - private final Optional detector; + private final CdiRepositoryContext context; private final BeanManager beanManager; private final String passivationId; private transient @Nullable T repoInstance; - private final SpringDataAnnotationBeanNameGenerator annotationBeanNameGenerator = new SpringDataAnnotationBeanNameGenerator(); - private final RepositoryBeanNameGenerator beanNameGenerator = new RepositoryBeanNameGenerator( - getClass().getClassLoader()); - /** * Creates a new {@link CdiRepositoryBean}. * @@ -84,7 +78,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl * @param beanManager the CDI {@link BeanManager}, must not be {@literal null}. */ public CdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager) { - this(qualifiers, repositoryType, beanManager, Optional.empty()); + this(qualifiers, repositoryType, beanManager, new CdiRepositoryContext(CdiRepositoryBean.class.getClassLoader())); } /** @@ -93,8 +87,7 @@ public CdiRepositoryBean(Set qualifiers, Class repositoryType, Be * @param qualifiers must not be {@literal null}. * @param repositoryType has to be an interface must not be {@literal null}. * @param beanManager the CDI {@link BeanManager}, must not be {@literal null}. - * @param detector detector for the custom repository implementations {@link CustomRepositoryImplementationDetector}, - * can be {@literal null}. + * @param detector detector for the custom repository implementations {@link CustomRepositoryImplementationDetector}. */ public CdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager, Optional detector) { @@ -107,7 +100,32 @@ public CdiRepositoryBean(Set qualifiers, Class repositoryType, Be this.qualifiers = qualifiers; this.repositoryType = repositoryType; this.beanManager = beanManager; - this.detector = detector; + this.context = new CdiRepositoryContext(getClass().getClassLoader(), detector + .orElseThrow(() -> new IllegalArgumentException("CustomRepositoryImplementationDetector must be present!"))); + this.passivationId = createPassivationId(qualifiers, repositoryType); + } + + /** + * Creates a new {@link CdiRepositoryBean}. + * + * @param qualifiers must not be {@literal null}. + * @param repositoryType has to be an interface must not be {@literal null}. + * @param beanManager the CDI {@link BeanManager}, must not be {@literal null}. + * @param context CDI context encapsulating class loader, metadata scanning and fragment detection. + * @since 2.1 + */ + public CdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager, + CdiRepositoryContext context) { + + Assert.notNull(qualifiers, "Qualifiers must not be null!"); + Assert.notNull(beanManager, "BeanManager must not be null!"); + Assert.notNull(repositoryType, "Repoitory type must not be null!"); + Assert.isTrue(repositoryType.isInterface(), "RepositoryType must be an interface!"); + + this.qualifiers = qualifiers; + this.repositoryType = repositoryType; + this.beanManager = beanManager; + this.context = context; this.passivationId = createPassivationId(qualifiers, repositoryType); } @@ -128,7 +146,6 @@ private String createPassivationId(Set qualifiers, Class reposito Collections.sort(qualifierNames); return StringUtils.collectionToDelimitedString(qualifierNames, ":") + ":" + repositoryType.getName(); - } /* @@ -215,89 +232,6 @@ public void destroy(@SuppressWarnings("null") T instance, creationalContext.release(); } - /** - * Looks up an instance of a {@link CdiRepositoryConfiguration}. In case the instance cannot be found within the CDI - * scope, a default configuration is used. - * - * @return an available CdiRepositoryConfiguration instance or a default configuration. - */ - protected CdiRepositoryConfiguration lookupConfiguration(BeanManager beanManager, Set qualifiers) { - - return beanManager.getBeans(CdiRepositoryConfiguration.class, getQualifiersArray(qualifiers)).stream().findFirst()// - .map(it -> (CdiRepositoryConfiguration) getDependencyInstance(it)) // - .orElse(DEFAULT_CONFIGURATION); - } - - /** - * Try to lookup a custom implementation for a {@link org.springframework.data.repository.Repository}. Can only be - * used when a {@code CustomRepositoryImplementationDetector} is provided. - * - * @param repositoryType - * @param beanManager - * @param qualifiers - * @return the custom implementation instance or null - */ - private Optional> getCustomImplementationBean(Class repositoryType, BeanManager beanManager, - Set qualifiers) { - - return detector.flatMap(it -> { - - CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); - - return getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration, it)// - .flatMap(type -> beanManager.getBeans(type, getQualifiersArray(qualifiers)).stream().findFirst()); - }); - } - - /** - * Retrieves a custom repository interfaces from a repository type. This works for the whole class hierarchy and can - * find also a custom repository which is inherited over many levels. - * - * @param repositoryType The class representing the repository. - * @param cdiRepositoryConfiguration The configuration for CDI usage. - * @return the interface class or {@literal null}. - */ - private Optional> getCustomImplementationClass(Class repositoryType, - CdiRepositoryConfiguration cdiRepositoryConfiguration, CustomRepositoryImplementationDetector detector) { - - String className = getCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration); - Optional beanDefinition = detector.detectCustomImplementation( // - className, // - getCustomImplementationBeanName(repositoryType), // - Collections.singleton(repositoryType.getPackage().getName()), // - Collections.emptySet(), // - beanNameGenerator::generateBeanName // - ); - - return beanDefinition.map(it -> { - - try { - return Class.forName(it.getBeanClassName()); - } catch (ClassNotFoundException e) { - throw new UnsatisfiedResolutionException( - String.format("Unable to resolve class for '%s'", it.getBeanClassName()), e); - } - }); - } - - private String getCustomImplementationBeanName(Class repositoryType) { - return annotationBeanNameGenerator.generateBeanName(new AnnotatedGenericBeanDefinition(repositoryType)) - + DEFAULT_CONFIGURATION.getRepositoryImplementationPostfix(); - } - - private String getCustomImplementationClassName(Class repositoryType, - CdiRepositoryConfiguration cdiRepositoryConfiguration) { - - String configuredPostfix = cdiRepositoryConfiguration.getRepositoryImplementationPostfix(); - Assert.hasText(configuredPostfix, "Configured repository postfix must not be null or empty!"); - - return ClassUtils.getShortName(repositoryType) + configuredPostfix; - } - - private Annotation[] getQualifiersArray(Set qualifiers) { - return qualifiers.toArray(new Annotation[qualifiers.size()]); - } - /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getQualifiers() @@ -380,18 +314,75 @@ public String getId() { * @param creationalContext will never be {@literal null}. * @param repositoryType will never be {@literal null}. * @return - * @deprecated override {@link #create(CreationalContext, Class, Object)} instead. */ - @Deprecated protected T create(CreationalContext creationalContext, Class repositoryType) { - Optional> customImplementationBean = getCustomImplementationBean(repositoryType, beanManager, qualifiers); - Optional customImplementation = customImplementationBean - .map(it -> beanManager.getReference(it, it.getBeanClass(), beanManager.createCreationalContext(it))); + CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); + + Optional> customImplementationBean = getCustomImplementationBean(repositoryType, + cdiRepositoryConfiguration); + Optional customImplementation = customImplementationBean.map(this::getDependencyInstance); return create(creationalContext, repositoryType, customImplementation); } + /** + * Lookup repository fragments for a {@link Class repository interface}. + * + * @param repositoryType must not be {@literal null}. + * @return the {@link RepositoryFragments}. + * @since 2.1 + */ + protected RepositoryFragments getRepositoryFragments(Class repositoryType) { + + Assert.notNull(repositoryType, "Repository type must not be null!"); + + CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); + + Optional> customImplementationBean = getCustomImplementationBean(repositoryType, + cdiRepositoryConfiguration); + Optional customImplementation = customImplementationBean.map(this::getDependencyInstance); + + List> repositoryFragments = getRepositoryFragments(repositoryType, + cdiRepositoryConfiguration); + + RepositoryFragments customImplementationFragment = customImplementation // + .map(RepositoryFragments::just) // + .orElseGet(RepositoryFragments::empty); + + return RepositoryFragments.from(repositoryFragments) // + .append(customImplementationFragment); + } + + @SuppressWarnings("unchecked") + private List> getRepositoryFragments(Class repositoryType, + CdiRepositoryConfiguration cdiRepositoryConfiguration) { + + Stream fragmentConfigurations = context + .getRepositoryFragments(cdiRepositoryConfiguration, repositoryType); + + return fragmentConfigurations.flatMap(it -> { + + Class interfaceClass = (Class) lookupFragmentInterface(repositoryType, it.getInterfaceName()); + Class implementationClass = context.loadClass(it.getClassName()); + Optional> bean = getBean(implementationClass, beanManager, qualifiers); + + return bean.map(this::getDependencyInstance) // + .map(implementation -> RepositoryFragment.implemented(interfaceClass, implementation)) // + .map(Stream::of) // + .orElse(Stream.empty()); + }).collect(Collectors.toList()); + } + + private static Class lookupFragmentInterface(Class repositoryType, String interfaceName) { + + return Arrays.stream(repositoryType.getInterfaces()) // + .filter(it -> it.getName().equals(interfaceName)) // + .findFirst() // + .orElseThrow(() -> new IllegalArgumentException(String.format("Did not find type %s in %s!", interfaceName, + Arrays.asList(repositoryType.getInterfaces())))); + } + /** * Creates the actual component instance. * @@ -399,7 +390,10 @@ protected T create(CreationalContext creationalContext, Class repositoryTy * @param repositoryType will never be {@literal null}. * @param customImplementation can be {@literal null}. * @return + * @deprecated since 2.1, override {@link #create(CreationalContext, Class)} in which you create a repository factory + * and call {@link #create(RepositoryFactorySupport, Class, RepositoryFragments)}. */ + @Deprecated protected T create(CreationalContext creationalContext, Class repositoryType, Optional customImplementation) { throw new UnsupportedOperationException( @@ -407,6 +401,35 @@ protected T create(CreationalContext creationalContext, Class repositoryTy + "in order to use custom repository implementations"); } + /** + * Looks up an instance of a {@link CdiRepositoryConfiguration}. In case the instance cannot be found within the CDI + * scope, a default configuration is used. + * + * @return an available CdiRepositoryConfiguration instance or a default configuration. + */ + protected CdiRepositoryConfiguration lookupConfiguration(BeanManager beanManager, Set qualifiers) { + + return beanManager.getBeans(CdiRepositoryConfiguration.class, getQualifiersArray(qualifiers)).stream().findFirst()// + .map(it -> (CdiRepositoryConfiguration) getDependencyInstance(it)) // + .orElse(DEFAULT_CONFIGURATION); + } + + /** + * Try to lookup a custom implementation for a {@link org.springframework.data.repository.Repository}. Can only be + * used when a {@code CustomRepositoryImplementationDetector} is provided. + * + * @param repositoryType + * @param beanManager + * @param qualifiers + * @return the custom implementation instance or null + */ + private Optional> getCustomImplementationBean(Class repositoryType, + CdiRepositoryConfiguration cdiRepositoryConfiguration) { + + return context.getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration)// + .flatMap(type -> getBean(type, beanManager, qualifiers)); + } + /** * Applies the configuration from {@link CdiRepositoryConfiguration} to {@link RepositoryFactorySupport} by looking up * the actual configuration. @@ -435,6 +458,26 @@ protected static void applyConfiguration(RepositoryFactorySupport repositoryFact configuration.getRepositoryBeanClass().ifPresent(repositoryFactory::setRepositoryBaseClass); } + /** + * Creates the actual repository instance. + * + * @param repositoryType will never be {@literal null}. + * @param repositoryFragments will never be {@literal null}. + * @return + */ + protected static T create(RepositoryFactorySupport repositoryFactory, Class repositoryType, + RepositoryFragments repositoryFragments) { + return repositoryFactory.getRepository(repositoryType, repositoryFragments); + } + + private static Optional> getBean(Class beanType, BeanManager beanManager, Set qualifiers) { + return beanManager.getBeans(beanType, getQualifiersArray(qualifiers)).stream().findFirst(); + } + + private static Annotation[] getQualifiersArray(Set qualifiers) { + return qualifiers.toArray(new Annotation[qualifiers.size()]); + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryContext.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryContext.java new file mode 100644 index 0000000000..1c8b95d8e2 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryContext.java @@ -0,0 +1,217 @@ +/* + * Copyright 2018 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.cdi; + +import lombok.RequiredArgsConstructor; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.stream.Stream; + +import javax.enterprise.inject.CreationException; +import javax.enterprise.inject.UnsatisfiedResolutionException; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.type.ClassMetadata; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; +import org.springframework.data.repository.config.FragmentMetadata; +import org.springframework.data.repository.config.RepositoryFragmentConfiguration; +import org.springframework.data.repository.config.RepositoryFragmentDiscovery; +import org.springframework.data.util.Optionals; +import org.springframework.data.util.Streamable; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Context for CDI repositories. This class provides {@link ClassLoader} and + * {@link org.springframework.data.repository.core.support.RepositoryFragment detection} which are commonly used within + * CDI. + * + * @author Mark Paluch + * @since 2.1 + */ +public class CdiRepositoryContext { + + private final ClassLoader classLoader; + private final CustomRepositoryImplementationDetector detector; + private final MetadataReaderFactory metadataReaderFactory; + + /** + * Create a new {@link CdiRepositoryContext} given {@link ClassLoader} and initialize + * {@link CachingMetadataReaderFactory}. + * + * @param classLoader must not be {@literal null}. + */ + public CdiRepositoryContext(ClassLoader classLoader) { + + Assert.notNull(classLoader, "ClassLoader must not be null!"); + + this.classLoader = classLoader; + + Environment environment = new StandardEnvironment(); + ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(classLoader); + + this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); + this.detector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment, resourceLoader); + } + + /** + * Create a new {@link CdiRepositoryContext} given {@link ClassLoader} and + * {@link CustomRepositoryImplementationDetector}. + * + * @param classLoader must not be {@literal null}. + * @param detector must not be {@literal null}. + */ + public CdiRepositoryContext(ClassLoader classLoader, CustomRepositoryImplementationDetector detector) { + + Assert.notNull(classLoader, "ClassLoader must not be null!"); + Assert.notNull(detector, "CustomRepositoryImplementationDetector must not be null!"); + + ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(classLoader); + + this.classLoader = classLoader; + this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); + this.detector = detector; + } + + CustomRepositoryImplementationDetector getCustomRepositoryImplementationDetector() { + return detector; + } + + /** + * Load a {@link Class} using the CDI {@link ClassLoader}. + * + * @param className + * @return + * @throws UnsatisfiedResolutionException if the class cannot be found. + */ + Class loadClass(String className) { + + try { + return ClassUtils.forName(className, classLoader); + } catch (ClassNotFoundException e) { + throw new UnsatisfiedResolutionException(String.format("Unable to resolve class for '%s'", className), e); + } + } + + /** + * Discover {@link RepositoryFragmentConfiguration fragment configurations} for a {@link Class repository interface}. + * + * @param configuration must not be {@literal null}. + * @param repositoryInterface must not be {@literal null}. + * @return {@link Stream} of {@link RepositoryFragmentConfiguration fragment configurations}. + */ + Stream getRepositoryFragments(CdiRepositoryConfiguration configuration, + Class repositoryInterface) { + + ClassMetadata classMetadata = getClassMetadata(metadataReaderFactory, repositoryInterface.getName()); + + RepositoryFragmentDiscovery fragmentConfiguration = new CdiRepositoryFragmentDiscovery(configuration); + + return Arrays.stream(classMetadata.getInterfaceNames()) // + .filter(it -> FragmentMetadata.isCandidate(it, metadataReaderFactory)) // + .map(it -> FragmentMetadata.of(it, fragmentConfiguration)) // + .map(this::detectRepositoryFragmentConfiguration) // + .flatMap(Optionals::toStream); + } + + /** + * Retrieves a custom repository interfaces from a repository type. This works for the whole class hierarchy and can + * find also a custom repository which is inherited over many levels. + * + * @param repositoryType The class representing the repository. + * @param cdiRepositoryConfiguration The configuration for CDI usage. + * @return the interface class or {@literal null}. + */ + Optional> getCustomImplementationClass(Class repositoryType, + CdiRepositoryConfiguration cdiRepositoryConfiguration) { + + String className = getCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration); + + Optional beanDefinition = detector.detectCustomImplementation( // + className, // + className, Collections.singleton(repositoryType.getPackage().getName()), // + Collections.emptySet(), // + BeanDefinition::getBeanClassName); + + return beanDefinition.map(it -> loadClass(it.getBeanClassName())); + } + + private Optional detectRepositoryFragmentConfiguration( + FragmentMetadata configuration) { + + String className = configuration.getFragmentImplementationClassName(); + + Optional beanDefinition = detector.detectCustomImplementation(className, null, + configuration.getBasePackages(), configuration.getExclusions(), BeanDefinition::getBeanClassName); + + return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(configuration.getFragmentInterfaceName(), bd)); + } + + private static ClassMetadata getClassMetadata(MetadataReaderFactory metadataReaderFactory, String className) { + + try { + return metadataReaderFactory.getMetadataReader(className).getClassMetadata(); + } catch (IOException e) { + throw new CreationException(String.format("Cannot parse %s metadata.", className), e); + } + } + + private static String getCustomImplementationClassName(Class repositoryType, + CdiRepositoryConfiguration cdiRepositoryConfiguration) { + + String configuredPostfix = cdiRepositoryConfiguration.getRepositoryImplementationPostfix(); + Assert.hasText(configuredPostfix, "Configured repository postfix must not be null or empty!"); + + return ClassUtils.getShortName(repositoryType) + configuredPostfix; + } + + @RequiredArgsConstructor + private static class CdiRepositoryFragmentDiscovery implements RepositoryFragmentDiscovery { + + private final CdiRepositoryConfiguration configuration; + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryFragmentDiscovery#getExcludeFilters() + */ + @Override + public Streamable getExcludeFilters() { + return Streamable.of(new AnnotationTypeFilter(NoRepositoryBean.class)); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryFragmentDiscovery#getRepositoryImplementationPostfix() + */ + @Override + public Optional getRepositoryImplementationPostfix() { + return Optional.of(configuration.getRepositoryImplementationPostfix()); + } + } +} diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java index 7b0c36ab81..5ba1a0d1c5 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java @@ -36,12 +36,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.env.Environment; -import org.springframework.core.env.StandardEnvironment; -import org.springframework.core.io.ResourceLoader; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.core.type.classreading.CachingMetadataReaderFactory; -import org.springframework.core.type.classreading.MetadataReaderFactory; import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.Repository; import org.springframework.data.repository.RepositoryDefinition; @@ -61,16 +55,10 @@ public abstract class CdiRepositoryExtensionSupport implements Extension { private final Map, Set> repositoryTypes = new HashMap<>(); private final Set> eagerRepositories = new HashSet<>(); - private final CustomRepositoryImplementationDetector customImplementationDetector; + private final CdiRepositoryContext context; protected CdiRepositoryExtensionSupport() { - - Environment environment = new StandardEnvironment(); - ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(getClass().getClassLoader()); - MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); - - this.customImplementationDetector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment, - resourceLoader); + context = new CdiRepositoryContext(getClass().getClassLoader()); } /** @@ -91,8 +79,8 @@ protected void processAnnotatedType(@Observes ProcessAnnotatedType proces Set qualifiers = getQualifiers(repositoryType); if (LOGGER.isDebugEnabled()) { - LOGGER.debug(String.format("Discovered repository type '%s' with qualifiers %s.", repositoryType.getName(), - qualifiers)); + LOGGER.debug( + String.format("Discovered repository type '%s' with qualifiers %s.", repositoryType.getName(), qualifiers)); } // Store the repository type using its qualifiers. repositoryTypes.put(repositoryType, qualifiers); @@ -184,7 +172,15 @@ protected void registerBean(CdiRepositoryBean bean) { * @return the {@link CustomRepositoryImplementationDetector} to scan for the custom implementation */ protected CustomRepositoryImplementationDetector getCustomImplementationDetector() { - return customImplementationDetector; + return context.getCustomRepositoryImplementationDetector(); + } + + /** + * @return the {@link CdiRepositoryContext} encapsulating the CDI-specific class loaders and fragment scanning. + * @since 2.1 + */ + protected CdiRepositoryContext getRepositoryContext() { + return context; } @SuppressWarnings("all") diff --git a/src/main/java/org/springframework/data/repository/config/FragmentMetadata.java b/src/main/java/org/springframework/data/repository/config/FragmentMetadata.java new file mode 100644 index 0000000000..a71a484870 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/FragmentMetadata.java @@ -0,0 +1,107 @@ +/* + * Copyright 2018 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 lombok.Value; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +import org.springframework.beans.factory.BeanDefinitionStoreException; +import org.springframework.core.type.AnnotationMetadata; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.repository.NoRepositoryBean; +import org.springframework.data.util.StreamUtils; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; + +/** + * Value object for a discovered Repository fragment interface. + * + * @author Mark Paluch + * @since 2.1 + */ +@Value(staticConstructor = "of") +public class FragmentMetadata { + + private String fragmentInterfaceName; + private RepositoryFragmentDiscovery configuration; + + /** + * Returns whether the given interface is a fragment candidate. + * + * @param interfaceName must not be {@literal null} or empty. + * @param factory must not be {@literal null}. + * @return + */ + public static boolean isCandidate(String interfaceName, MetadataReaderFactory factory) { + + Assert.hasText(interfaceName, "Interface name must not be null or empty!"); + Assert.notNull(factory, "MetadataReaderFactory must not be null!"); + + AnnotationMetadata metadata = getAnnotationMetadata(interfaceName, factory); + + return !metadata.hasAnnotation(NoRepositoryBean.class.getName()); + } + + /** + * Returns the exclusions to be used when scanning for fragment implementations. + * + * @return + */ + public List getExclusions() { + + Stream configurationExcludes = configuration.getExcludeFilters().stream(); + Stream noRepositoryBeans = Stream.of(new AnnotationTypeFilter(NoRepositoryBean.class)); + + return Stream.concat(configurationExcludes, noRepositoryBeans).collect(StreamUtils.toUnmodifiableList()); + } + + /** + * Returns the name of the implementation class to be detected for the fragment interface. + * + * @return + */ + public String getFragmentImplementationClassName() { + + String postfix = configuration.getRepositoryImplementationPostfix().orElse("Impl"); + + return ClassUtils.getShortName(fragmentInterfaceName).concat(postfix); + } + + /** + * Returns the base packages to be scanned to find implementations of the current fragment interface. + * + * @return + */ + public Iterable getBasePackages() { + return Collections.singleton(ClassUtils.getPackageName(fragmentInterfaceName)); + } + + private static AnnotationMetadata getAnnotationMetadata(String className, + MetadataReaderFactory metadataReaderFactory) { + + try { + return metadataReaderFactory.getMetadataReader(className).getAnnotationMetadata(); + } catch (IOException e) { + throw new BeanDefinitionStoreException(String.format("Cannot parse %s metadata.", className), e); + } + } +} 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 1d1500ba7d..dad778e539 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java @@ -15,42 +15,35 @@ */ package org.springframework.data.repository.config; -import lombok.Value; +import lombok.RequiredArgsConstructor; import java.io.IOException; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.BeanDefinitionStoreException; -import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; -import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.ClassMetadata; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.core.type.filter.AnnotationTypeFilter; import org.springframework.core.type.filter.TypeFilter; import org.springframework.data.config.ParsingUtils; -import org.springframework.data.repository.NoRepositoryBean; import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.core.support.RepositoryFragmentsFactoryBean; import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider; import org.springframework.data.util.Optionals; -import org.springframework.data.util.StreamUtils; +import org.springframework.data.util.Streamable; import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; /** * Builder to create {@link BeanDefinitionBuilder} instance to eventually create Spring Data repository instances. @@ -180,23 +173,24 @@ private Stream registerRepositoryFragmentsImple RepositoryConfiguration configuration) { ClassMetadata classMetadata = getClassMetadata(configuration.getRepositoryInterface()); + RepositoryFragmentDiscovery fragmentConfiguration = new DefaultRepositoryFragmentDiscovery(configuration); return Arrays.stream(classMetadata.getInterfaceNames()) // .filter(it -> FragmentMetadata.isCandidate(it, metadataReaderFactory)) // - .map(it -> FragmentMetadata.of(it, configuration)) // - .map(it -> detectRepositoryFragmentConfiguration(it)) // - .flatMap(it -> Optionals.toStream(it)) // + .map(it -> FragmentMetadata.of(it, fragmentConfiguration)) // + .map(it -> detectRepositoryFragmentConfiguration(it, configuration.getConfigurationSource())) // + .flatMap(Optionals::toStream) // .peek(it -> potentiallyRegisterFragmentImplementation(configuration, it)) // .peek(it -> potentiallyRegisterRepositoryFragment(configuration, it)); } private Optional detectRepositoryFragmentConfiguration( - FragmentMetadata configuration) { + FragmentMetadata configuration, RepositoryConfigurationSource configurationSource) { String className = configuration.getFragmentImplementationClassName(); Optional beanDefinition = implementationDetector.detectCustomImplementation(className, null, - configuration.getBasePackages(), configuration.getExclusions(), configuration.getBeanNameGenerator()); + configuration.getBasePackages(), configuration.getExclusions(), configurationSource::generateBeanName); return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(configuration.getFragmentInterfaceName(), bd)); } @@ -256,81 +250,27 @@ private ClassMetadata getClassMetadata(String className) { } } - @Value(staticConstructor = "of") - static class FragmentMetadata { + @RequiredArgsConstructor + private static class DefaultRepositoryFragmentDiscovery implements RepositoryFragmentDiscovery { - String fragmentInterfaceName; - RepositoryConfiguration configuration; + private final RepositoryConfiguration configuration; - /** - * Returns whether the given interface is a fragment candidate. - * - * @param interfaceName must not be {@literal null} or empty. - * @param factory must not be {@literal null}. - * @return + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryFragmentDiscovery#getExcludeFilters() */ - public static boolean isCandidate(String interfaceName, MetadataReaderFactory factory) { - - Assert.hasText(interfaceName, "Interface name must not be null or empty!"); - Assert.notNull(factory, "MetadataReaderFactory must not be null!"); - - AnnotationMetadata metadata = getAnnotationMetadata(interfaceName, factory); - - return !metadata.hasAnnotation(NoRepositoryBean.class.getName()); - } - - /** - * Returns the exclusions to be used when scanning for fragment implementations. - * - * @return - */ - public List getExclusions() { - - Stream configurationExcludes = configuration.getExcludeFilters().stream(); - Stream noRepositoryBeans = Stream.of(new AnnotationTypeFilter(NoRepositoryBean.class)); - - return Stream.concat(configurationExcludes, noRepositoryBeans).collect(StreamUtils.toUnmodifiableList()); - } - - /** - * Returns the name of the implementation class to be detected for the fragment interface. - * - * @return - */ - public String getFragmentImplementationClassName() { - - RepositoryConfigurationSource configurationSource = configuration.getConfigurationSource(); - String postfix = configurationSource.getRepositoryImplementationPostfix().orElse("Impl"); - - return ClassUtils.getShortName(fragmentInterfaceName).concat(postfix); - } - - /** - * Returns the base packages to be scanned to find implementations of the current fragment interface. - * - * @return - */ - public Iterable getBasePackages() { - return Collections.singleton(ClassUtils.getPackageName(fragmentInterfaceName)); + @Override + public Streamable getExcludeFilters() { + return configuration.getExcludeFilters(); } - /** - * Returns the bean name generating function to be used for the fragment. - * - * @return + /* + * (non-Javadoc) + * @see org.springframework.data.repository.config.RepositoryFragmentDiscovery#getRepositoryImplementationPostfix() */ - public Function getBeanNameGenerator() { - return definition -> configuration.getConfigurationSource().generateBeanName(definition); - } - - private static AnnotationMetadata getAnnotationMetadata(String className, - MetadataReaderFactory metadataReaderFactory) { - - try { - return metadataReaderFactory.getMetadataReader(className).getAnnotationMetadata(); - } catch (IOException e) { - throw new BeanDefinitionStoreException(String.format("Cannot parse %s metadata.", className), e); - } + @Override + public Optional getRepositoryImplementationPostfix() { + return configuration.getConfigurationSource().getRepositoryImplementationPostfix(); } } } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryFragmentDiscovery.java b/src/main/java/org/springframework/data/repository/config/RepositoryFragmentDiscovery.java new file mode 100644 index 0000000000..d249885987 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/RepositoryFragmentDiscovery.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 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.util.Optional; + +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.data.util.Streamable; + +/** + * Interface containing the configurable options for the Spring Data repository fragment subsystem. + * + * @author Mark Paluch + * @since 2.1 + * @see RepositoryConfigurationSource + */ +public interface RepositoryFragmentDiscovery { + + /** + * Returns the {@link TypeFilter}s to be used to exclude packages from repository scanning. + * + * @return + */ + Streamable getExcludeFilters(); + + /** + * Returns the configured postfix to be used for looking up custom implementation classes. + * + * @return the postfix to use or {@link Optional#empty()} in case none is configured. + */ + Optional getRepositoryImplementationPostfix(); +} diff --git a/src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterface.java b/src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterface.java new file mode 100644 index 0000000000..93536c5cf3 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterface.java @@ -0,0 +1,26 @@ +/* + * Copyright 2018 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.cdi; + +/** + * @author Mark Paluch + */ +public interface AnotherFragmentInterface { + + int getPriority(); + + int getShadowed(); +} diff --git a/src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterfaceImpl.java b/src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterfaceImpl.java new file mode 100644 index 0000000000..1b7621f569 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/AnotherFragmentInterfaceImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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.cdi; + +/** + * @author Mark Paluch + */ +class AnotherFragmentInterfaceImpl implements AnotherFragmentInterface { + + @Override + public int getPriority() { + return 2; + } + + @Override + public int getShadowed() { + return 2; + } +} diff --git a/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.java b/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.java index 33e5075f4e..b2fa86af1e 100644 --- a/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.java +++ b/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2018 the original author or authors. + * Copyright 2018 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. @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.data.repository.cdi; -public class AnotherRepositoryImpl implements AnotherRepositoryCustom { +/** + * @author Mark Paluch + */ +class AnotherRepositoryImpl implements AnotherRepositoryCustom { @Override public int returnZero() { diff --git a/src/test/java/org/springframework/data/repository/cdi/CdiConfigurationIntegrationTests.java b/src/test/java/org/springframework/data/repository/cdi/CdiConfigurationIntegrationTests.java new file mode 100644 index 0000000000..3de518d30f --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/CdiConfigurationIntegrationTests.java @@ -0,0 +1,58 @@ +/* + * Copyright 2018 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.cdi; + +import static org.assertj.core.api.Assertions.*; + +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.data.repository.cdi.isolated.IsolatedComposedRepository; + +/** + * CDI integration tests for {@link CdiRepositoryConfiguration}. + * + * @author Mark Paluch + */ +public class CdiConfigurationIntegrationTests { + + private static SeContainer container; + + @BeforeClass + public static void setUp() { + + container = SeContainerInitializer.newInstance() // + .disableDiscovery() // + .addPackages(IsolatedComposedRepository.class) // + .initialize(); + } + + @Test // DATACMNS-1233 + public void shouldApplyImplementationPostfix() { + + IsolatedComposedRepository repository = container.select(IsolatedComposedRepository.class).get(); + + assertThat(repository.getPriority()).isEqualTo(42); + } + + @AfterClass + public static void tearDown() { + container.close(); + } +} diff --git a/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java b/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java index f12411b55b..7784df06c3 100755 --- a/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java +++ b/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryBeanUnitTests.java @@ -155,14 +155,14 @@ protected SampleRepository create( // verify(detector).detectCustomImplementation( // eq("CdiRepositoryBeanUnitTests.SampleRepositoryImpl"), // - eq("namedRepositoryImpl"), // + eq("CdiRepositoryBeanUnitTests.SampleRepositoryImpl"), // anySet(), // anySet(), // Mockito.any(Function.class) // ); } - @Test // DATACMNS-1255 + @Test // DATACMNS-1233 public void appliesRepositoryConfiguration() { DummyCdiRepositoryBean bean = new DummyCdiRepositoryBean(NO_ANNOTATIONS, diff --git a/src/test/java/org/springframework/data/repository/cdi/ComposedRepository.java b/src/test/java/org/springframework/data/repository/cdi/ComposedRepository.java new file mode 100644 index 0000000000..eca46ab681 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/ComposedRepository.java @@ -0,0 +1,31 @@ +/* + * Copyright 2018 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.cdi; + +import java.io.Serializable; + +import org.springframework.data.repository.Repository; + +/** + * @author Mark Paluch + */ +public interface ComposedRepository + extends Repository, FragmentInterface, AnotherFragmentInterface { + + // duplicate method shadowed by AnotherFragmentInterfaceImpl. The legacy custom implementation comes last, after all + // other fragments. + int getShadowed(); +} diff --git a/src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryCustom.java b/src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryCustom.java new file mode 100644 index 0000000000..c0e79b5f01 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryCustom.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 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.cdi; + +/** + * @author Mark Paluch + */ +public interface ComposedRepositoryCustom { + + int returnFourtyTwo(); + + // duplicate method shadowed by AnotherFragmentInterfaceImpl. The legacy custom implementation comes last, after all + // other + // fragments. + int getShadowed(); +} diff --git a/src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryImpl.java b/src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryImpl.java new file mode 100644 index 0000000000..06b3fbb720 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/ComposedRepositoryImpl.java @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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.cdi; + +/** + * @author Mark Paluch + */ +class ComposedRepositoryImpl implements ComposedRepositoryCustom { + + @Override + public int returnFourtyTwo() { + return 42; + } + + @Override + public int getShadowed() { + return 1; + } +} diff --git a/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java b/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java index fd681dde52..f6eb4c240a 100644 --- a/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java +++ b/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java @@ -24,16 +24,15 @@ import java.util.Set; import javax.enterprise.context.NormalScope; -import javax.enterprise.context.spi.Contextual; import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Observes; import javax.enterprise.inject.spi.AfterBeanDiscovery; import javax.enterprise.inject.spi.BeanManager; import org.apache.webbeans.context.AbstractContext; -import org.apache.webbeans.context.creational.BeanInstanceBag; import org.mockito.Mockito; import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; +import org.springframework.data.repository.core.support.DummyRepositoryFactory; /** * Dummy extension of {@link CdiRepositoryExtensionSupport} to allow integration tests. Will create mocks for repository @@ -74,9 +73,12 @@ public Class getScope() { } @Override - protected T create(CreationalContext creationalContext, Class repositoryType, - Optional customImplementation) { - return Mockito.mock(repositoryType); + protected T create(CreationalContext creationalContext, Class repositoryType) { + + T mock = Mockito.mock(repositoryType); + DummyRepositoryFactory repositoryFactory = new DummyRepositoryFactory(mock); + + return create(repositoryFactory, repositoryType, getRepositoryFragments(repositoryType)); } } diff --git a/src/test/java/org/springframework/data/repository/cdi/FragmentInterface.java b/src/test/java/org/springframework/data/repository/cdi/FragmentInterface.java new file mode 100644 index 0000000000..5102c92a2e --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/FragmentInterface.java @@ -0,0 +1,23 @@ +/* + * Copyright 2018 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.cdi; + +/** + * @author Mark Paluch + */ +public interface FragmentInterface { + int getPriority(); +} diff --git a/src/test/java/org/springframework/data/repository/cdi/FragmentInterfaceImpl.java b/src/test/java/org/springframework/data/repository/cdi/FragmentInterfaceImpl.java new file mode 100644 index 0000000000..912530af11 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/FragmentInterfaceImpl.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 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.cdi; + +/** + * @author Mark Paluch + */ +class FragmentInterfaceImpl implements FragmentInterface { + + @Override + public int getPriority() { + return 1; + } +} diff --git a/src/test/java/org/springframework/data/repository/cdi/RepositoryFragmentsIntegrationTests.java b/src/test/java/org/springframework/data/repository/cdi/RepositoryFragmentsIntegrationTests.java new file mode 100644 index 0000000000..477679ef1c --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/RepositoryFragmentsIntegrationTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2018 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.cdi; + +import static org.assertj.core.api.Assertions.*; + +import javax.enterprise.inject.se.SeContainer; +import javax.enterprise.inject.se.SeContainerInitializer; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * CDI integration tests for composed repositories. + * + * @author Mark Paluch + */ +public class RepositoryFragmentsIntegrationTests { + + private static SeContainer container; + + @BeforeClass + public static void setUp() { + + container = SeContainerInitializer.newInstance() // + .disableDiscovery() // + .addPackages(ComposedRepository.class) // + .initialize(); + } + + @Test // DATACMNS-1233 + public void shouldInvokeCustomImplementationLast() { + + ComposedRepository repository = getBean(ComposedRepository.class); + ComposedRepositoryImpl customImplementation = getBean(ComposedRepositoryImpl.class); + AnotherFragmentInterfaceImpl shadowed = getBean(AnotherFragmentInterfaceImpl.class); + + assertThat(repository.getShadowed()).isEqualTo(2); + assertThat(customImplementation.getShadowed()).isEqualTo(1); + assertThat(shadowed.getShadowed()).isEqualTo(2); + } + + @Test // DATACMNS-1233 + public void shouldRespectInterfaceOrder() { + + ComposedRepository repository = getBean(ComposedRepository.class); + FragmentInterfaceImpl fragment = getBean(FragmentInterfaceImpl.class); + AnotherFragmentInterfaceImpl shadowed = getBean(AnotherFragmentInterfaceImpl.class); + + assertThat(repository.getPriority()).isEqualTo(1); + assertThat(fragment.getPriority()).isEqualTo(1); + assertThat(shadowed.getPriority()).isEqualTo(2); + } + + protected T getBean(Class type) { + return container.select(type).get(); + } + + @AfterClass + public static void tearDown() { + container.close(); + } +} diff --git a/src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterface.java b/src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterface.java new file mode 100644 index 0000000000..5399c18137 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterface.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 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.cdi.isolated; + +/** + * @author Mark Paluch + */ +public interface FragmentInterface { + + int getPriority(); +} diff --git a/src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterfaceFoo.java b/src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterfaceFoo.java new file mode 100644 index 0000000000..79d134587b --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/isolated/FragmentInterfaceFoo.java @@ -0,0 +1,27 @@ +/* + * Copyright 2018 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.cdi.isolated; + +/** + * @author Mark Paluch + */ +class FragmentInterfaceFoo implements FragmentInterface { + + @Override + public int getPriority() { + return 42; + } +} diff --git a/src/test/java/org/springframework/data/repository/cdi/isolated/IsolatedComposedRepository.java b/src/test/java/org/springframework/data/repository/cdi/isolated/IsolatedComposedRepository.java new file mode 100644 index 0000000000..6d039a2f13 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/isolated/IsolatedComposedRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2018 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.cdi.isolated; + +import java.io.Serializable; + +import org.springframework.data.repository.Repository; + +/** + * @author Mark Paluch + */ +public interface IsolatedComposedRepository extends Repository, FragmentInterface {} diff --git a/src/test/java/org/springframework/data/repository/cdi/isolated/MyCdiConfiguration.java b/src/test/java/org/springframework/data/repository/cdi/isolated/MyCdiConfiguration.java new file mode 100644 index 0000000000..e2be6b7395 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/isolated/MyCdiConfiguration.java @@ -0,0 +1,29 @@ +/* + * Copyright 2018 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.cdi.isolated; + +import org.springframework.data.repository.cdi.CdiRepositoryConfiguration; + +/** + * @author Mark Paluch + */ +public class MyCdiConfiguration implements CdiRepositoryConfiguration { + + @Override + public String getRepositoryImplementationPostfix() { + return "Foo"; + } +}