DATACMNS-565 - Improved detection of custom implementations for CDI repositories.

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.
This commit is contained in:
Mark Paluch
2014-08-20 21:34:01 +02:00
committed by Oliver Gierke
parent 8bfa0bd9e0
commit 6cc9e33359
3 changed files with 129 additions and 118 deletions

View File

@@ -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<T> implements Bean<T>, PassivationCapable {
private static final Logger LOGGER = LoggerFactory.getLogger(CdiRepositoryBean.class);
private static final CdiRepositoryConfiguration DEFAULT_CONFIGURATION = DefaultCdiRepositoryConfiguration.INSTANCE;
private final Set<Annotation> qualifiers;
private final Class<T> 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<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 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<Annotation> qualifiers, Class<T> repositoryType, BeanManager beanManager,
Bean<?> customImplementationBean) {
CustomRepositoryImplementationDetector detector) {
Assert.notNull(qualifiers);
Assert.notNull(beanManager);
@@ -88,7 +94,7 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, 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<T> implements Bean<T>, 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<Annotation> qualifiers) {
Set<Bean<?>> beans = beanManager.getBeans(CdiRepositoryConfiguration.class, getQualifiersArray(qualifiers));
if (beans.isEmpty()) {
return DEFAULT_CONFIGURATION;
}
Bean<CdiRepositoryConfiguration> bean = (Bean<CdiRepositoryConfiguration>) beans.iterator().next();
return getDependencyInstance(bean, (Class<CdiRepositoryConfiguration>) 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<Annotation> qualifiers) {
if (detector == null) {
return null;
}
CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers);
Class<?> customImplementationClass = getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration);
if (customImplementationClass == 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.
*
* @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<Annotation> 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<T> implements Bean<T>, PassivationCapabl
return ApplicationScoped.class;
}
/*
/*
* (non-Javadoc)
* @see javax.enterprise.inject.spi.PassivationCapable#getId()
*/
@@ -272,6 +364,7 @@ public abstract class CdiRepositoryBean<T> implements Bean<T>, PassivationCapabl
@Deprecated
protected T create(CreationalContext<T> creationalContext, Class<T> 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<T> implements Bean<T>, 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;
}
}
}

View File

@@ -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<Class<?>, Set<Annotation>> repositoryTypes = new HashMap<Class<?>, Set<Annotation>>();
private final Set<CdiRepositoryBean<?>> eagerRepositories = new HashSet<CdiRepositoryBean<?>>();
@@ -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<Annotation> qualifiers) {
Set<Bean<?>> 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<Annotation> 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<Annotation> qualifiers) {
CdiRepositoryConfiguration cdiRepositoryConfiguration = lookupConfiguration(beanManager, qualifiers);
Class<?> customImplementationClass = getCustomImplementationClass(repositoryType, cdiRepositoryConfiguration);
if (customImplementationClass == 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.
*
* @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;
}
}
}