DATACMNS-557 - Polishing.

Renamed CdiRepositoryConfigurationSource to CdiRepositoryConfiguration. Turned anonymous inner default implementation into dedicated enum to avoid repeated instantiation.

Changed the CdiRepositoryBean to rather take a Bean instance of the custom implementation instead of the resolved object to make sure both instances (the custom implementation and the repository proxy) are instantiated at the same time.

Fixed imports. Some early returns to avoid unnecessary nesting.

Adapted architecture description to allow CDI module accessing the configuration module.

Related pull request: #92.
This commit is contained in:
Oliver Gierke
2014-08-07 12:09:34 +02:00
parent 1f0260e48d
commit 756c951112
6 changed files with 168 additions and 126 deletions

View File

@@ -51,7 +51,7 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapabl
private final Set<Annotation> qualifiers;
private final Class<T> repositoryType;
private final Object customImplementation;
private final Bean<?> customImplementationBean;
private final BeanManager beanManager;
private final String passivationId;
@@ -74,9 +74,11 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, 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 customImplementation the custom implementation of the {@link org.springframework.data.repository.Repository}, can be {@literal null}.
* @param customImplementationBean the bean for the custom implementation of the
* {@link org.springframework.data.repository.Repository}, can be {@literal null}.
*/
public CdiRepositoryBean(Set<Annotation> qualifiers, Class<T> repositoryType, BeanManager beanManager, Object customImplementation) {
public CdiRepositoryBean(Set<Annotation> qualifiers, Class<T> repositoryType, BeanManager beanManager,
Bean<?> customImplementationBean) {
Assert.notNull(qualifiers);
Assert.notNull(beanManager);
@@ -86,7 +88,7 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapabl
this.qualifiers = qualifiers;
this.repositoryType = repositoryType;
this.beanManager = beanManager;
this.customImplementation = customImplementation;
this.customImplementationBean = customImplementationBean;
this.passivationId = createPassivationId(qualifiers, repositoryType);
}
@@ -265,8 +267,15 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapabl
* @param creationalContext will never be {@literal null}.
* @param repositoryType will never be {@literal null}.
* @return
* @deprecated overide {@link #create(CreationalContext, Class, Object)} instead.
*/
@Deprecated
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType) {
Object customImplementation = customImplementationBean == null ? null : beanManager.getReference(
customImplementationBean, customImplementationBean.getBeanClass(),
beanManager.createCreationalContext(customImplementationBean));
return create(creationalContext, repositoryType, customImplementation);
}
@@ -279,8 +288,8 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapabl
* @return
*/
protected T create(CreationalContext<T> creationalContext, Class<T> repositoryType, Object customImplementation) {
throw new UnsupportedOperationException("You have to implement create(CreationalContext<T>, Class<T>, Object) " +
"in order to use custom repository implementations");
throw new UnsupportedOperationException("You have to implement create(CreationalContext<T>, Class<T>, Object) "
+ "in order to use custom repository implementations");
}
/*
@@ -289,6 +298,7 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapabl
*/
@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());
}
}

View File

@@ -21,7 +21,7 @@ package org.springframework.data.repository.cdi;
*
* @author Mark Paluch
*/
public interface CdiRepositoryConfigurationSource {
public interface CdiRepositoryConfiguration {
/**
* Returns the configured postfix to be used for looking up custom implementation classes.

View File

@@ -15,17 +15,27 @@
*/
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.*;
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;
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;
@@ -42,8 +52,8 @@ 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;
import org.springframework.util.StringUtils;
/**
* Base class for {@link Extension} implementations that create instances for Spring Data repositories.
@@ -55,17 +65,20 @@ import org.springframework.util.StringUtils;
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<Class<?>, Set<Annotation>> repositoryTypes = new HashMap<Class<?>, Set<Annotation>>();
private final Set<CdiRepositoryBean<?>> eagerRepositories = new HashSet<CdiRepositoryBean<?>>();
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);
this.customImplementationDetector = new CustomRepositoryImplementationDetector(metadataReaderFactory, environment,
resourceLoader);
}
/**
@@ -176,26 +189,24 @@ 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.
* 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 CdiRepositoryConfigurationSource instance or a default configuration.
* @return an available CdiRepositoryConfiguration instance or a default configuration.
*/
protected CdiRepositoryConfigurationSource lookupConfiguration(BeanManager beanManager, Set<Annotation> qualifiers) {
Set<Bean<?>> beans = beanManager.getBeans(CdiRepositoryConfigurationSource.class, getQualifiersArray(qualifiers));
protected CdiRepositoryConfiguration lookupConfiguration(BeanManager beanManager, Set<Annotation> qualifiers) {
Set<Bean<?>> beans = beanManager.getBeans(CdiRepositoryConfiguration.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;
}
};
return DEFAULT_CONFIGURATION;
}
Bean<?> bean = beans.iterator().next();
CreationalContext<?> creationalContext = beanManager.createCreationalContext(bean);
return (CdiRepositoryConfigurationSource) beanManager.getReference(bean, CdiRepositoryConfigurationSource.class, creationalContext);
return (CdiRepositoryConfiguration) beanManager.getReference(bean, CdiRepositoryConfiguration.class,
creationalContext);
}
private Annotation[] getQualifiersArray(Set<Annotation> qualifiers) {
@@ -210,52 +221,54 @@ public abstract class CdiRepositoryExtensionSupport implements Extension {
* @param qualifiers
* @return the custom implementation instance or null
*/
protected Object findCustomImplementation(Class<?> repositoryType, BeanManager beanManager, Set<Annotation> qualifiers) {
protected Bean<?> getCustomImplementationBean(Class<?> repositoryType, BeanManager beanManager,
Set<Annotation> qualifiers) {
CdiRepositoryConfigurationSource cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers);
Class<?> customImplementationClass = findCustomImplementationClass(repositoryType, cdiRepositoryConfiguration);
if (customImplementationClass != null) {
Set<Bean<?>> 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);
}
CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers);
Class<?> customImplementationClass = getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration);
if (customImplementationClass == null) {
return null;
}
return null;
Set<Bean<?>> 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.
* 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.
* @return the interface class or {@literal null}.
*/
protected Class<?> findCustomImplementationClass(Class<?> repositoryType, CdiRepositoryConfigurationSource cdiRepositoryConfiguration) {
private Class<?> getCustomImplementationClass(Class<?> repositoryType,
CdiRepositoryConfiguration cdiRepositoryConfiguration) {
String className = constructCustomImplementationClassName(repositoryType, cdiRepositoryConfiguration);
String className = getCustomImplementationClassName(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);
}
if (beanDefinition == null) {
return 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 constructCustomImplementationClassName(Class<?> repositoryType, CdiRepositoryConfigurationSource cdiRepositoryConfiguration) {
private String getCustomImplementationClassName(Class<?> repositoryType,
CdiRepositoryConfiguration cdiRepositoryConfiguration) {
String configuredPostfix = cdiRepositoryConfiguration.getRepositoryImplementationPostfix();
String implementationPostfix = StringUtils.hasText(configuredPostfix) ? configuredPostfix : DefaultRepositoryConfiguration.DEFAULT_REPOSITORY_IMPLEMENTATION_POSTFIX;
return ClassUtils.getShortName(repositoryType) + implementationPostfix;
Assert.hasText(configuredPostfix, "Configured repository postfix must not be null or empty!");
return ClassUtils.getShortName(repositoryType) + configuredPostfix;
}
@SuppressWarnings("all")
@@ -271,4 +284,18 @@ public abstract class CdiRepositoryExtensionSupport implements Extension {
private static final long serialVersionUID = 7261821376671361463L;
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;
}
}
}

View File

@@ -35,81 +35,82 @@ import org.springframework.util.StringUtils;
/**
* Detects the custom implementation for a {@link org.springframework.data.repository.Repository}
*
* @author Oliver Gierke
* @author Mark Paluch
*/
public class CustomRepositoryImplementationDetector {
private static final String CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN = "**/*%s.class";
private static final String CUSTOM_IMPLEMENTATION_RESOURCE_PATTERN = "**/*%s.class";
private MetadataReaderFactory metadataReaderFactory;
private Environment environment;
private ResourceLoader resourceLoader;
private final MetadataReaderFactory metadataReaderFactory;
private final Environment environment;
private final ResourceLoader resourceLoader;
/**
*
* Creates a new {@link CustomRepositoryImplementationDetector} from the given {@link org.springframework.core.type.classreading.MetadataReaderFactory},
* 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) {
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!");
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;
}
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<String> basePackages) {
/**
* 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<String> basePackages) {
Assert.notNull(className, "ClassName must not be null!");
Assert.notNull(basePackages, "BasePackages must not be null!");
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 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));
// 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<BeanDefinition> definitions = new HashSet<BeanDefinition>();
Set<BeanDefinition> definitions = new HashSet<BeanDefinition>();
for (String basePackage : basePackages) {
definitions.addAll(provider.findCandidateComponents(basePackage));
}
for (String basePackage : basePackages) {
definitions.addAll(provider.findCandidateComponents(basePackage));
}
if (definitions.isEmpty()) {
return null;
}
if (definitions.isEmpty()) {
return null;
}
if (definitions.size() == 1) {
return (AbstractBeanDefinition) definitions.iterator().next();
}
if (definitions.size() == 1) {
return (AbstractBeanDefinition) definitions.iterator().next();
}
List<String> implementationClassNames = new ArrayList<String>();
for (BeanDefinition bean : definitions) {
implementationClassNames.add(bean.getBeanClassName());
}
List<String> implementationClassNames = new ArrayList<String>();
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)));
}
throw new IllegalStateException(String.format(
"Ambiguous custom implementations detected! Found %s but expected a single implementation!",
StringUtils.collectionToCommaDelimitedString(implementationClassNames)));
}
}