package com.lambdaworks.redis.support;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.spi.*;
import com.lambdaworks.redis.RedisClient;
import com.lambdaworks.redis.RedisURI;
import com.lambdaworks.redis.cluster.RedisClusterClient;
import com.lambdaworks.redis.internal.LettuceSets;
import com.lambdaworks.redis.resource.ClientResources;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
/**
* A portable CDI extension which registers beans for lettuce. If there are no RedisURIs there are also no registrations for
* {@link RedisClient RedisClients}. The extension allows to create {@link RedisClient} and {@link RedisClusterClient}
* instances. Client instances are provided under the same qualifiers as the {@link RedisURI}. {@link ClientResources} can be
* shared across multiple client instances (Standalone, Cluster) by providing a {@link ClientResources} bean with the same
* qualifiers as the {@link RedisURI}.
*
* <p>
* <strong>Example:</strong>
* </p>
*
* <pre>
* <code>
* public class Producers {
* @Produces
* public RedisURI redisURI() {
* return RedisURI.Builder.redis("localhost", 6379).build();
* }
*
* @Produces
* public ClientResources clientResources() {
* return DefaultClientResources.create()
* }
*
* public void shutdownClientResources(@Disposes ClientResources clientResources) throws Exception {
* clientResources.shutdown().get();
* }
* }
* </code>
* </pre>
*
*
* <pre>
* <code>
* public class Consumer {
* @Inject
* private RedisClient client;
*
* @Inject
* private RedisClusterClient clusterClient;
* }
* </code>
* </pre>
*
* @author Mark Paluch
*/
public class LettuceCdiExtension implements Extension {
private static final InternalLogger LOGGER = InternalLoggerFactory.getInstance(LettuceCdiExtension.class);
private final Map<Set<Annotation>, Bean<RedisURI>> redisUris = new ConcurrentHashMap<>();
private final Map<Set<Annotation>, Bean<ClientResources>> clientResources = new ConcurrentHashMap<>();
public LettuceCdiExtension() {
LOGGER.info("Activating CDI extension for lettuce.");
}
/**
* Implementation of a an observer which checks for RedisURI beans and stores them in {@link #redisUris} for later
* association with corresponding repository beans.
*
* @param <T> The type.
* @param processBean The annotated type as defined by CDI.
*/
@SuppressWarnings("unchecked")
<T> void processBean(@Observes ProcessBean<T> processBean) {
Bean<T> bean = processBean.getBean();
for (Type type : bean.getTypes()) {
if (!(type instanceof Class<?>)) {
continue;
}
// Check if the bean is an RedisURI.
if (RedisURI.class.isAssignableFrom((Class<?>) type)) {
Set<Annotation> qualifiers = LettuceSets.newHashSet(bean.getQualifiers());
if (bean.isAlternative() || !redisUris.containsKey(qualifiers)) {
LOGGER.debug(String.format("Discovered '%s' with qualifiers %s.", RedisURI.class.getName(), qualifiers));
redisUris.put(qualifiers, (Bean<RedisURI>) bean);
}
}
if (ClientResources.class.isAssignableFrom((Class<?>) type)) {
Set<Annotation> qualifiers = LettuceSets.newHashSet(bean.getQualifiers());
if (bean.isAlternative() || !clientResources.containsKey(qualifiers)) {
LOGGER.debug(String.format("Discovered '%s' with qualifiers %s.", ClientResources.class.getName(),
qualifiers));
clientResources.put(qualifiers, (Bean<ClientResources>) bean);
}
}
}
}
/**
* Implementation of a an observer which registers beans to the CDI container for the detected RedisURIs.
* <p>
* The repository beans are associated to the EntityManagers using their qualifiers.
*
* @param beanManager The BeanManager instance.
*/
void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscovery, BeanManager beanManager) {
int counter = 0;
for (Entry<Set<Annotation>, Bean<RedisURI>> entry : redisUris.entrySet()) {
Bean<RedisURI> redisUri = entry.getValue();
Set<Annotation> qualifiers = entry.getKey();
String clientBeanName = RedisClient.class.getSimpleName();
String clusterClientBeanName = RedisClusterClient.class.getSimpleName();
if (!containsDefault(qualifiers)) {
clientBeanName += counter;
clusterClientBeanName += counter;
counter++;
}
Bean<ClientResources> clientResources = this.clientResources.get(qualifiers);
RedisClientCdiBean clientBean = new RedisClientCdiBean(redisUri, clientResources, beanManager, qualifiers,
clientBeanName);
register(afterBeanDiscovery, qualifiers, clientBean);
RedisClusterClientCdiBean clusterClientBean = new RedisClusterClientCdiBean(redisUri, clientResources, beanManager,
qualifiers, clusterClientBeanName);
register(afterBeanDiscovery, qualifiers, clusterClientBean);
}
}
private boolean containsDefault(Set<Annotation> qualifiers) {
return qualifiers.stream().filter(input -> input instanceof Default).findFirst().isPresent();
}
private void register(AfterBeanDiscovery afterBeanDiscovery, Set<Annotation> qualifiers, Bean<?> bean) {
LOGGER.info(String.format("Registering bean '%s' with qualifiers %s.", bean.getBeanClass().getName(), qualifiers));
afterBeanDiscovery.addBean(bean);
}
}