From 1f0260e48d7af7c8f37d7ebbcb26c9cb4c24c4cc Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 7 Aug 2014 10:26:10 +0200 Subject: [PATCH] DATACMNS-557 - Added support for custom implementations to CDI infrastructure. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CDI extension now discovers a custom implementation class similarly to the detection when using Spring Data in a Spring container. Extracted the custom implementation detection for re-use. The CDI extension discovers the potentially available bean and hands it to the CdiRepositoryBean for further usage. Implementations should implement the extended variant of the create(…) method we introduce with this commit to be able to forward the resolved custom implementation bean. Original pull request: #92. --- .../repository/cdi/CdiRepositoryBean.java | 41 +++++- .../cdi/CdiRepositoryConfigurationSource.java | 32 +++++ .../cdi/CdiRepositoryExtensionSupport.java | 122 ++++++++++++++++-- ...ustomRepositoryImplementationDetector.java | 115 +++++++++++++++++ .../DefaultRepositoryConfiguration.java | 2 +- .../RepositoryBeanDefinitionBuilder.java | 64 +-------- .../repository/cdi/AnotherRepository.java | 25 ++++ .../cdi/AnotherRepositoryCustom.java | 21 +++ .../repository/cdi/AnotherRepositoryImpl.java | 25 ++++ ...itoryExtensionSupportIntegrationTests.java | 17 ++- .../repository/cdi/DummyCdiExtension.java | 46 +++++-- .../data/repository/cdi/RepositoryClient.java | 3 + 12 files changed, 427 insertions(+), 86 deletions(-) create mode 100644 src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfigurationSource.java create mode 100644 src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/AnotherRepository.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryCustom.java create mode 100644 src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.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 448b597e9..91ef6d841 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java @@ -43,6 +43,7 @@ import org.springframework.util.StringUtils; * * @author Dirk Mahler * @author Oliver Gierke + * @author Mark Paluch */ public abstract class CdiRepositoryBean implements Bean, PassivationCapable { @@ -50,6 +51,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl private final Set qualifiers; private final Class repositoryType; + private final Object customImplementation; private final BeanManager beanManager; private final String passivationId; @@ -63,6 +65,18 @@ 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, null); + } + + /** + * 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 customImplementation the custom implementation of the {@link org.springframework.data.repository.Repository}, can be {@literal null}. + */ + public CdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager, Object customImplementation) { Assert.notNull(qualifiers); Assert.notNull(beanManager); @@ -72,6 +86,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl this.qualifiers = qualifiers; this.repositoryType = repositoryType; this.beanManager = beanManager; + this.customImplementation = customImplementation; this.passivationId = createPassivationId(qualifiers, repositoryType); } @@ -162,7 +177,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl creationalContext.release(); } - /* + /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getQualifiers() */ @@ -228,7 +243,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl return Collections.emptySet(); } - /* + /* * (non-Javadoc) * @see javax.enterprise.inject.spi.Bean#getScope() */ @@ -251,15 +266,29 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl * @param repositoryType will never be {@literal null}. * @return */ - protected abstract T create(CreationalContext creationalContext, Class repositoryType); + protected T create(CreationalContext creationalContext, Class repositoryType) { + return create(creationalContext, repositoryType, customImplementation); + } - /* + /** + * Creates the actual component instance. + * + * @param creationalContext will never be {@literal null}. + * @param repositoryType will never be {@literal null}. + * @param customImplementation can be {@literal null}. + * @return + */ + protected T create(CreationalContext creationalContext, Class repositoryType, Object customImplementation) { + throw new UnsupportedOperationException("You have to implement create(CreationalContext, Class, Object) " + + "in order to use custom repository implementations"); + } + + /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { - return String - .format("JpaRepositoryBean: type='%s', qualifiers=%s", repositoryType.getName(), qualifiers.toString()); + return String.format("JpaRepositoryBean: type='%s', qualifiers=%s", repositoryType.getName(), qualifiers.toString()); } } diff --git a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfigurationSource.java new file mode 100644 index 000000000..6e6f5271c --- /dev/null +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryConfigurationSource.java @@ -0,0 +1,32 @@ +/* + * Copyright 2014 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; + +/** + * Interface containing the configurable options for the Spring Data repository subsystem using CDI. + * + * @author Mark Paluch + */ +public interface CdiRepositoryConfigurationSource { + + /** + * Returns the configured postfix to be used for looking up custom implementation classes. + * + * @return the postfix to use or {@literal null} in case none is configured. + */ + String 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 df211f8a5..ad97517a4 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java @@ -15,36 +15,42 @@ */ package org.springframework.data.repository.cdi; -import java.lang.annotation.Annotation; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - +import javax.enterprise.context.spi.CreationalContext; import javax.enterprise.event.Observes; import javax.enterprise.inject.Any; import javax.enterprise.inject.Default; -import javax.enterprise.inject.spi.AfterDeploymentValidation; -import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.BeanManager; -import javax.enterprise.inject.spi.Extension; -import javax.enterprise.inject.spi.ProcessAnnotatedType; +import javax.enterprise.inject.UnsatisfiedResolutionException; +import javax.enterprise.inject.spi.*; import javax.enterprise.util.AnnotationLiteral; import javax.inject.Qualifier; +import java.lang.annotation.Annotation; +import java.util.*; +import java.util.Map.Entry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; 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; +import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; +import org.springframework.data.repository.config.DefaultRepositoryConfiguration; +import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * Base class for {@link Extension} implementations that create instances for Spring Data repositories. * * @author Dirk Mahler * @author Oliver Gierke + * @author Mark Paluch */ public abstract class CdiRepositoryExtensionSupport implements Extension { @@ -52,6 +58,15 @@ public abstract class CdiRepositoryExtensionSupport implements Extension { private final Map, Set> repositoryTypes = new HashMap, Set>(); private final Set> eagerRepositories = new HashSet>(); + private final CustomRepositoryImplementationDetector customImplementationDetector; + + protected CdiRepositoryExtensionSupport() { + Environment environment = new StandardEnvironment(); + ResourceLoader resourceLoader = new PathMatchingResourcePatternResolver(getClass().getClassLoader()); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); + customImplementationDetector = new CustomRepositoryImplementationDetector(metadataReaderFactory, + environment, resourceLoader); + } /** * Implementation of a an observer which checks for Spring Data repository types and stores them in @@ -160,6 +175,89 @@ public abstract class CdiRepositoryExtensionSupport implements Extension { } } + /** + * Looks up an instance of a {@link CdiRepositoryConfigurationSource}. In case the instance cannot be found within + * the CDI scope, a default configuration is used. + * + * @return an available CdiRepositoryConfigurationSource instance or a default configuration. + */ + protected CdiRepositoryConfigurationSource lookupConfiguration(BeanManager beanManager, Set qualifiers) { + Set> beans = beanManager.getBeans(CdiRepositoryConfigurationSource.class, getQualifiersArray(qualifiers)); + if (beans.isEmpty()) { + // no own defined type since these would be picked up by CDI by default. + return new CdiRepositoryConfigurationSource() { + @Override + public String getRepositoryImplementationPostfix() { + return DefaultRepositoryConfiguration.DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX; + } + }; + } + + Bean bean = beans.iterator().next(); + CreationalContext creationalContext = beanManager.createCreationalContext(bean); + return (CdiRepositoryConfigurationSource) beanManager.getReference(bean, CdiRepositoryConfigurationSource.class, creationalContext); + } + + private Annotation[] getQualifiersArray(Set qualifiers) { + return qualifiers.toArray(new Annotation[qualifiers.size()]); + } + + /** + * Try to lookup a custom implementation for a {@link org.springframework.data.repository.Repository}. + * + * @param repositoryType + * @param beanManager + * @param qualifiers + * @return the custom implementation instance or null + */ + protected Object findCustomImplementation(Class repositoryType, BeanManager beanManager, Set qualifiers) { + + CdiRepositoryConfigurationSource cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); + Class customImplementationClass = findCustomImplementationClass(repositoryType, cdiRepositoryConfiguration); + if (customImplementationClass != null) { + Set> beans = beanManager.getBeans(customImplementationClass, getQualifiersArray(qualifiers)); + if (!beans.isEmpty()) { + Bean bean = beans.iterator().next(); + CreationalContext creationalContext = beanManager.createCreationalContext(bean); + return beanManager.getReference(bean, customImplementationClass, creationalContext); + } + } + + return null; + } + + /** + * Retrieves a custom repository interfaces from a repository type. This works for the whole class hierarchy and can find + * also a custom repo which is inherieted over many levels. + * + * @param repositoryType The class representing the repository. + * @param cdiRepositoryConfiguration The configuration for CDI usage. + * @return the interface class or null. + */ + protected Class findCustomImplementationClass(Class repositoryType, CdiRepositoryConfigurationSource cdiRepositoryConfiguration) { + + String className = constructCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration); + AbstractBeanDefinition beanDefinition = customImplementationDetector.detectCustomImplementation(className, + Collections.singleton(repositoryType.getPackage().getName())); + + if (beanDefinition != null) { + try { + return Class.forName(beanDefinition.getBeanClassName()); + } catch (ClassNotFoundException e) { + throw new UnsatisfiedResolutionException(String.format("Unable to resolve class for '%s'", + beanDefinition.getBeanClassName()), e); + } + } + + return null; + } + + private String constructCustomImplementationClassName(Class repositoryType, CdiRepositoryConfigurationSource cdiRepositoryConfiguration) { + String configuredPostfix = cdiRepositoryConfiguration.getRepositoryImplementationPostfix(); + String implementationPostfix = StringUtils.hasText(configuredPostfix) ? configuredPostfix : DefaultRepositoryConfiguration.DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX; + return ClassUtils.getShortName(repositoryType) + implementationPostfix; + } + @SuppressWarnings("all") static class DefaultAnnotationLiteral extends AnnotationLiteral implements Default { diff --git a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java new file mode 100644 index 000000000..ebdff8037 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java @@ -0,0 +1,115 @@ +/* + * Copyright 2014 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.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.env.Environment; +import org.springframework.core.io.ResourceLoader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.RegexPatternTypeFilter; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Detects the custom implementation for a {@link org.springframework.data.repository.Repository} + * + * @author Mark Paluch + */ +public class CustomRepositoryImplementationDetector { + + private static final String CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN = "**/*%s.class"; + + private MetadataReaderFactory metadataReaderFactory; + private Environment environment; + private ResourceLoader resourceLoader; + + /** + * + * Creates a new {@link CustomRepositoryImplementationDetector} from the given {@link org.springframework.core.type.classreading.MetadataReaderFactory}, + * {@link org.springframework.core.env.Environment} and {@link org.springframework.core.io.ResourceLoader}. + * + * @param metadataReaderFactory must not be {@literal null}. + * @param environment must not be {@literal null}. + * @param resourceLoader must not be {@literal null}. + */ + public CustomRepositoryImplementationDetector(MetadataReaderFactory metadataReaderFactory, Environment environment, + ResourceLoader resourceLoader) { + + Assert.notNull(metadataReaderFactory, "MetadataReaderFactory must not be null!"); + Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); + Assert.notNull(environment, "Environment must not be null!"); + + this.metadataReaderFactory = metadataReaderFactory; + this.environment = environment; + this.resourceLoader = resourceLoader; + } + + /** + * Tries to detect a custom implementation for a repository bean by classpath scanning. + * + * @param className must not be {@literal null}. + * @param basePackages must not be {@literal null}. + * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found + */ + public AbstractBeanDefinition detectCustomImplementation(String className, Iterable basePackages) { + + Assert.notNull(className, "ClassName must not be null!"); + Assert.notNull(basePackages, "BasePackages must not be null!"); + + // Build pattern to lookup implementation class + Pattern pattern = Pattern.compile(".*\\." + className); + + // Build classpath scanner and lookup bean definition + ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); + provider.setEnvironment(environment); + provider.setResourceLoader(resourceLoader); + provider.setResourcePattern(String.format(CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN, className)); + provider.setMetadataReaderFactory(metadataReaderFactory); + provider.addIncludeFilter(new RegexPatternTypeFilter(pattern)); + + Set definitions = new HashSet(); + + for (String basePackage : basePackages) { + definitions.addAll(provider.findCandidateComponents(basePackage)); + } + + if (definitions.isEmpty()) { + return null; + } + + if (definitions.size() == 1) { + return (AbstractBeanDefinition) definitions.iterator().next(); + } + + List implementationClassNames = new ArrayList(); + for (BeanDefinition bean : definitions) { + implementationClassNames.add(bean.getBeanClassName()); + } + + throw new IllegalStateException(String.format( + "Ambiguous custom implementations detected! Found %s but expected a single implementation!", + StringUtils.collectionToCommaDelimitedString(implementationClassNames))); + } +} 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 9e70c1322..f71c7f8ce 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java @@ -29,8 +29,8 @@ import org.springframework.util.StringUtils; public class DefaultRepositoryConfiguration implements RepositoryConfiguration { + public static final String DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX = "Impl"; private static final Key DEFAULT_QUERY_LOOKUP_STRATEGY = Key.CREATE_IF_NOT_FOUND; - private static final String DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX = "Impl"; private final T configurationSource; private final BeanDefinition definition; 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 cfe587ef2..a86dcac8e 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java @@ -15,25 +15,16 @@ */ package org.springframework.data.repository.config; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -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.context.annotation.ClassPathScanningCandidateComponentProvider; import org.springframework.core.env.Environment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.type.classreading.CachingMetadataReaderFactory; import org.springframework.core.type.classreading.MetadataReaderFactory; -import org.springframework.core.type.filter.RegexPatternTypeFilter; import org.springframework.data.repository.query.ExtensionAwareEvaluationContextProvider; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -46,14 +37,13 @@ import org.springframework.util.StringUtils; class RepositoryBeanDefinitionBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryBeanDefinitionBuilder.class); - private static final String CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN = "**/*%s.class"; private final BeanDefinitionRegistry registry; private final RepositoryConfigurationExtension extension; private final ResourceLoader resourceLoader; - private final Environment environment; private final MetadataReaderFactory metadataReaderFactory; + private CustomRepositoryImplementationDetector implementationDetector; /** * Creates a new {@link RepositoryBeanDefinitionBuilder} from the given {@link BeanDefinitionRegistry}, @@ -69,21 +59,20 @@ class RepositoryBeanDefinitionBuilder { Assert.notNull(extension, "RepositoryConfigurationExtension must not be null!"); Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); - Assert.notNull(environment, "Environement must not be null!"); + Assert.notNull(environment, "Environment must not be null!"); this.registry = registry; this.extension = extension; this.resourceLoader = resourceLoader; this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader); - this.environment = environment; + this.implementationDetector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment, resourceLoader); } /** * Builds a new {@link BeanDefinitionBuilder} from the given {@link BeanDefinitionRegistry} and {@link ResourceLoader} * . * - * @param registry must not be {@literal null}. - * @param resourceLoader must not be {@literal null}. + * @param configuration must not be {@literal null}. * @return */ public BeanDefinitionBuilder build(RepositoryConfiguration configuration) { @@ -136,7 +125,8 @@ class RepositoryBeanDefinitionBuilder { return beanName; } - AbstractBeanDefinition beanDefinition = detectCustomImplementation(configuration); + AbstractBeanDefinition beanDefinition = implementationDetector.detectCustomImplementation( + configuration.getImplementationClassName(), configuration.getBasePackages()); if (null == beanDefinition) { return null; @@ -154,47 +144,5 @@ class RepositoryBeanDefinitionBuilder { return beanName; } - /** - * Tries to detect a custom implementation for a repository bean by classpath scanning. - * - * @param config must not be {@literal null}. - * @return the {@code AbstractBeanDefinition} of the custom implementation or {@literal null} if none found - */ - private AbstractBeanDefinition detectCustomImplementation(RepositoryConfiguration configuration) { - // Build pattern to lookup implementation class - String className = configuration.getImplementationClassName(); - Pattern pattern = Pattern.compile(".*\\." + className); - - // Build classpath scanner and lookup bean definition - ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); - provider.setEnvironment(environment); - provider.setResourceLoader(resourceLoader); - provider.setResourcePattern(String.format(CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN, className)); - provider.setMetadataReaderFactory(metadataReaderFactory); - provider.addIncludeFilter(new RegexPatternTypeFilter(pattern)); - - Set definitions = new HashSet(); - - for (String basePackage : configuration.getBasePackages()) { - definitions.addAll(provider.findCandidateComponents(basePackage)); - } - - if (definitions.isEmpty()) { - return null; - } - - if (definitions.size() == 1) { - return (AbstractBeanDefinition) definitions.iterator().next(); - } - - List implementationClassNames = new ArrayList(); - for (BeanDefinition bean : definitions) { - implementationClassNames.add(bean.getBeanClassName()); - } - - throw new IllegalStateException(String.format( - "Ambiguous custom implementations detected! Found %s but expected a single implementation!", - StringUtils.collectionToCommaDelimitedString(implementationClassNames))); - } } diff --git a/src/test/java/org/springframework/data/repository/cdi/AnotherRepository.java b/src/test/java/org/springframework/data/repository/cdi/AnotherRepository.java new file mode 100644 index 000000000..feeeda0c6 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/AnotherRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 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; + +public interface AnotherRepository extends Repository, AnotherRepositoryCustom { + +} diff --git a/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryCustom.java b/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryCustom.java new file mode 100644 index 000000000..1c50e68d0 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryCustom.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 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; + +public interface AnotherRepositoryCustom { + int returnZero(); +} diff --git a/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.java b/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.java new file mode 100644 index 000000000..e8a0448f8 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/cdi/AnotherRepositoryImpl.java @@ -0,0 +1,25 @@ +/* + * Copyright 2014 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; + +public class AnotherRepositoryImpl implements AnotherRepositoryCustom { + + @Override + public int returnZero() { + return 0; + } +} diff --git a/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupportIntegrationTests.java b/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupportIntegrationTests.java index 5c37cf5ae..feabed15f 100644 --- a/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupportIntegrationTests.java +++ b/src/test/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupportIntegrationTests.java @@ -15,8 +15,8 @@ */ package org.springframework.data.repository.cdi; -import static org.junit.Assert.*; import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; import org.junit.Test; @@ -36,5 +36,20 @@ public abstract class CdiRepositoryExtensionSupportIntegrationTests { assertThat(client.repository, is(notNullValue())); } + /** + * @see DATACMNS-557 + */ + @Test + public void createsSpringDataRepositoryWithCustimImplBean() { + + assertThat(getBean(AnotherRepository.class), is(notNullValue())); + + RepositoryClient client = getBean(RepositoryClient.class); + assertThat(client.anotherRepository, is(notNullValue())); + + // this will always return 0 since it's a mock + assertThat(client.anotherRepository.returnZero(), is(0)); + } + protected abstract T getBean(Class type); } 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 767e23734..54d5bf9de 100644 --- a/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java +++ b/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java @@ -15,18 +15,21 @@ */ package org.springframework.data.repository.cdi; -import java.lang.annotation.Annotation; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Map.Entry; -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 java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.Set; +import org.apache.webbeans.context.AbstractContext; +import org.apache.webbeans.context.creational.BeanInstanceBag; import org.mockito.Mockito; /** @@ -37,10 +40,15 @@ import org.mockito.Mockito; */ public class DummyCdiExtension extends CdiRepositoryExtensionSupport { - @SuppressWarnings({ "rawtypes", "unchecked" }) + @SuppressWarnings({"rawtypes", "unchecked"}) void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { + afterBeanDiscovery.addContext(new MyCustomScope()); for (Entry, Set> type : getRepositoryTypes()) { - DummyCdiRepositoryBean bean = new DummyCdiRepositoryBean(type.getValue(), type.getKey(), beanManager); + + CdiRepositoryConfigurationSource configurationSource = lookupConfiguration(beanManager, type.getValue()); + Object customImplementation = findCustomImplementation(type.getKey(), beanManager, type.getValue()); + + DummyCdiRepositoryBean bean = new DummyCdiRepositoryBean(type.getValue(), type.getKey(), beanManager, customImplementation); registerBean(bean); afterBeanDiscovery.addBean(bean); } @@ -52,6 +60,10 @@ public class DummyCdiExtension extends CdiRepositoryExtensionSupport { super(qualifiers, repositoryType, beanManager); } + DummyCdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager, Object customImplementation) { + super(qualifiers, repositoryType, beanManager, customImplementation); + } + public Class getScope() { return MyScope.class; } @@ -67,4 +79,22 @@ public class DummyCdiExtension extends CdiRepositoryExtensionSupport { @interface MyScope { } + + static class MyCustomScope extends AbstractContext { + + MyCustomScope() { + super(MyScope.class); + setActive(true); + } + + @Override + protected void setComponentInstanceMap() { + componentInstanceMap = new HashMap, BeanInstanceBag>(); + } + + @Override + public boolean isActive() { + return true; + } + } } diff --git a/src/test/java/org/springframework/data/repository/cdi/RepositoryClient.java b/src/test/java/org/springframework/data/repository/cdi/RepositoryClient.java index a43c75d7b..fef98a289 100644 --- a/src/test/java/org/springframework/data/repository/cdi/RepositoryClient.java +++ b/src/test/java/org/springframework/data/repository/cdi/RepositoryClient.java @@ -25,4 +25,7 @@ class RepositoryClient { @Inject SampleRepository repository; + + @Inject + AnotherRepository anotherRepository; }