From 6cc9e33359895b6a2b6b1ba3150bbbb2f03d56c8 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Wed, 20 Aug 2014 21:34:01 +0200 Subject: [PATCH] DATACMNS-565 - Improved detection of custom implementations for CDI repositories. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The detection of the custom implementation has now been moved into the Bean base class to avoid calls to BeanManager.getBeans(…) (which are necessary to pick up the configuration class defining postfixes and the like) happen to early in the CDI container lifecycle. Related ticket: DATACMNS-557. --- .../repository/cdi/CdiRepositoryBean.java | 127 ++++++++++++++++-- .../cdi/CdiRepositoryExtensionSupport.java | 106 +-------------- .../repository/cdi/DummyCdiExtension.java | 14 +- 3 files changed, 129 insertions(+), 118 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 ed0422ac5..a65a8f459 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryBean.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -28,6 +28,7 @@ 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; @@ -35,7 +36,11 @@ import javax.enterprise.inject.spi.PassivationCapable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.support.AbstractBeanDefinition; +import org.springframework.data.repository.config.CustomRepositoryImplementationDetector; +import org.springframework.data.repository.config.DefaultRepositoryConfiguration; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; /** @@ -48,10 +53,11 @@ import org.springframework.util.StringUtils; public abstract class CdiRepositoryBean implements Bean, PassivationCapable { private static final Logger LOGGER = LoggerFactory.getLogger(CdiRepositoryBean.class); + private static final CdiRepositoryConfiguration DEFAULT_CONFIGURATION = DefaultCdiRepositoryConfiguration.INSTANCE; private final Set qualifiers; private final Class repositoryType; - private final Bean customImplementationBean; + private final CustomRepositoryImplementationDetector detector; private final BeanManager beanManager; private final String passivationId; @@ -74,11 +80,11 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl * @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 customImplementationBean the bean for the custom implementation of the - * {@link org.springframework.data.repository.Repository}, can be {@literal null}. + * @param detector detector for the custom repository implementations {@link CustomRepositoryImplementationDetector}, + * can be {@literal null}. */ public CdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager, - Bean customImplementationBean) { + CustomRepositoryImplementationDetector detector) { Assert.notNull(qualifiers); Assert.notNull(beanManager); @@ -88,7 +94,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl this.qualifiers = qualifiers; this.repositoryType = repositoryType; this.beanManager = beanManager; - this.customImplementationBean = customImplementationBean; + this.detector = detector; this.passivationId = createPassivationId(qualifiers, repositoryType); } @@ -179,7 +185,93 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl 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. + */ + @SuppressWarnings("unchecked") + protected CdiRepositoryConfiguration lookupConfiguration(BeanManager beanManager, Set qualifiers) { + + Set> beans = beanManager.getBeans(CdiRepositoryConfiguration.class, getQualifiersArray(qualifiers)); + + if (beans.isEmpty()) { + return DEFAULT_CONFIGURATION; + } + + Bean bean = (Bean) beans.iterator().next(); + return getDependencyInstance(bean, (Class) bean.getBeanClass()); + } + + /** + * 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 Bean getCustomImplementationBean(Class repositoryType, BeanManager beanManager, + Set qualifiers) { + + if (detector == null) { + return null; + } + + CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); + Class customImplementationClass = getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration); + + if (customImplementationClass == null) { + return null; + } + + Set> beans = beanManager.getBeans(customImplementationClass, getQualifiersArray(qualifiers)); + return beans.isEmpty() ? null : beans.iterator().next(); + } + + /** + * 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 {@literal null}. + */ + private Class getCustomImplementationClass(Class repositoryType, + CdiRepositoryConfiguration cdiRepositoryConfiguration) { + + String className = getCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration); + AbstractBeanDefinition beanDefinition = detector.detectCustomImplementation(className, + Collections.singleton(repositoryType.getPackage().getName())); + + if (beanDefinition == null) { + return null; + } + + try { + return Class.forName(beanDefinition.getBeanClassName()); + } catch (ClassNotFoundException e) { + throw new UnsatisfiedResolutionException(String.format("Unable to resolve class for '%s'", + beanDefinition.getBeanClassName()), e); + } + } + + 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() */ @@ -253,7 +345,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl return ApplicationScoped.class; } - /* + /* * (non-Javadoc) * @see javax.enterprise.inject.spi.PassivationCapable#getId() */ @@ -272,6 +364,7 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl @Deprecated protected T create(CreationalContext creationalContext, Class repositoryType) { + Bean customImplementationBean = getCustomImplementationBean(repositoryType, beanManager, qualifiers); Object customImplementation = customImplementationBean == null ? null : beanManager.getReference( customImplementationBean, customImplementationBean.getBeanClass(), beanManager.createCreationalContext(customImplementationBean)); @@ -292,13 +385,27 @@ public abstract class CdiRepositoryBean implements Bean, PassivationCapabl + "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()); + .format("CdiRepositoryBean: type='%s', qualifiers=%s", repositoryType.getName(), qualifiers.toString()); + } + + static 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/CdiRepositoryExtensionSupport.java b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java index 2baba6fb8..05c00c14d 100644 --- a/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java +++ b/src/main/java/org/springframework/data/repository/cdi/CdiRepositoryExtensionSupport.java @@ -1,5 +1,5 @@ /* - * Copyright 2011-2013 the original author or authors. + * Copyright 2011-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. @@ -16,21 +16,17 @@ package org.springframework.data.repository.cdi; import java.lang.annotation.Annotation; -import java.util.Collections; 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.UnsatisfiedResolutionException; import javax.enterprise.inject.spi.AfterDeploymentValidation; import javax.enterprise.inject.spi.AnnotatedType; -import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.Extension; import javax.enterprise.inject.spi.ProcessAnnotatedType; @@ -39,7 +35,6 @@ import javax.inject.Qualifier; 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; @@ -51,9 +46,6 @@ 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.Assert; -import org.springframework.util.ClassUtils; /** * Base class for {@link Extension} implementations that create instances for Spring Data repositories. @@ -65,7 +57,6 @@ import org.springframework.util.ClassUtils; public abstract class CdiRepositoryExtensionSupport implements Extension { private static final Logger LOGGER = LoggerFactory.getLogger(CdiRepositoryExtensionSupport.class); - private static final CdiRepositoryConfiguration DEFAULT_CONFIGURATION = DefaultCdiRepositoryConfiguration.INSTANCE; private final Map, Set> repositoryTypes = new HashMap, Set>(); private final Set> eagerRepositories = new HashSet>(); @@ -189,86 +180,10 @@ public abstract class CdiRepositoryExtensionSupport implements Extension { } /** - * 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. + * @return the {@link CustomRepositoryImplementationDetector} to scan for the custom implementation */ - protected CdiRepositoryConfiguration lookupConfiguration(BeanManager beanManager, Set qualifiers) { - - Set> beans = beanManager.getBeans(CdiRepositoryConfiguration.class, getQualifiersArray(qualifiers)); - - if (beans.isEmpty()) { - return DEFAULT_CONFIGURATION; - } - - Bean bean = beans.iterator().next(); - CreationalContext creationalContext = beanManager.createCreationalContext(bean); - - return (CdiRepositoryConfiguration) beanManager.getReference(bean, CdiRepositoryConfiguration.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 Bean getCustomImplementationBean(Class repositoryType, BeanManager beanManager, - Set qualifiers) { - - CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers); - Class customImplementationClass = getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration); - - if (customImplementationClass == null) { - return null; - } - - Set> beans = beanManager.getBeans(customImplementationClass, getQualifiersArray(qualifiers)); - return beans.isEmpty() ? null : beans.iterator().next(); - } - - /** - * 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 {@literal null}. - */ - private Class getCustomImplementationClass(Class repositoryType, - CdiRepositoryConfiguration cdiRepositoryConfiguration) { - - String className = getCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration); - AbstractBeanDefinition beanDefinition = customImplementationDetector.detectCustomImplementation(className, - Collections.singleton(repositoryType.getPackage().getName())); - - if (beanDefinition == null) { - return null; - } - - try { - return Class.forName(beanDefinition.getBeanClassName()); - } catch (ClassNotFoundException e) { - throw new UnsatisfiedResolutionException(String.format("Unable to resolve class for '%s'", - beanDefinition.getBeanClassName()), e); - } - } - - 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; + protected CustomRepositoryImplementationDetector getCustomImplementationDetector() { + return customImplementationDetector; } @SuppressWarnings("all") @@ -285,17 +200,4 @@ public abstract class CdiRepositoryExtensionSupport implements Extension { private static final AnyAnnotationLiteral INSTANCE = new AnyAnnotationLiteral(); } - static 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/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java b/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java index 0450b06d9..8e85b76dc 100644 --- a/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java +++ b/src/test/java/org/springframework/data/repository/cdi/DummyCdiExtension.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2011-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. @@ -27,29 +27,31 @@ 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.Bean; 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; /** * Dummy extension of {@link CdiRepositoryExtensionSupport} to allow integration tests. Will create mocks for repository * interfaces being found. * * @author Oliver Gierke + * @author Mark Paluch */ public class DummyCdiExtension extends CdiRepositoryExtensionSupport { @SuppressWarnings({ "rawtypes", "unchecked" }) void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) { + afterBeanDiscovery.addContext(new MyCustomScope()); + for (Entry, Set> type : getRepositoryTypes()) { - Bean customImplementation = getCustomImplementationBean(type.getKey(), beanManager, type.getValue()); DummyCdiRepositoryBean bean = new DummyCdiRepositoryBean(type.getValue(), type.getKey(), beanManager, - customImplementation); + getCustomImplementationDetector()); registerBean(bean); afterBeanDiscovery.addBean(bean); } @@ -62,8 +64,8 @@ public class DummyCdiExtension extends CdiRepositoryExtensionSupport { } DummyCdiRepositoryBean(Set qualifiers, Class repositoryType, BeanManager beanManager, - Bean customImplementation) { - super(qualifiers, repositoryType, beanManager, customImplementation); + CustomRepositoryImplementationDetector detector) { + super(qualifiers, repositoryType, beanManager, detector); } public Class getScope() {