/*
* Copyright 2013 Martin Kouba
*
* 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.trimou.cdi.resolver;
import static org.trimou.engine.priority.Priorities.rightAfter;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import javax.enterprise.context.Dependent;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.AmbiguousResolutionException;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.trimou.cdi.BeanManagerLocator;
import org.trimou.engine.cache.ComputingCache;
import org.trimou.engine.config.ConfigurationKey;
import org.trimou.engine.config.SimpleConfigurationKey;
import org.trimou.engine.resolver.AbstractResolver;
import org.trimou.engine.resolver.Hints;
import org.trimou.engine.resolver.ReflectionResolver;
import org.trimou.engine.resolver.ResolutionContext;
import org.trimou.engine.resource.ReleaseCallback;
/**
* CDI beans resolver. Note that only beans with a name (i.e. annotated with
* {@link Named}) are resolvable.
* <p>
* Similarly to the CDI and Unified EL integration, instance of a dependent bean
* exists to service just a single tag evaluation.
*
* @author Martin Kouba
*/
public class CDIBeanResolver extends AbstractResolver {
private static final Logger LOGGER = LoggerFactory
.getLogger(CDIBeanResolver.class);
public static final String COMPUTING_CACHE_CONSUMER_ID = CDIBeanResolver.class
.getName();
public static final int CDI_BEAN_RESOLVER_PRIORITY = rightAfter(
ReflectionResolver.REFLECTION_RESOLVER_PRIORITY);
public static final ConfigurationKey BEAN_CACHE_MAX_SIZE_KEY = new SimpleConfigurationKey(
CDIBeanResolver.class.getName() + ".beanCacheMaxSize", 1000L);
private BeanManager beanManager;
private ComputingCache<String, Optional<Bean<?>>> beanCache;
/**
*
*/
public CDIBeanResolver() {
this(null, CDI_BEAN_RESOLVER_PRIORITY);
}
/**
*
* @param priority
*/
public CDIBeanResolver(int priority) {
this(null, priority);
}
/**
*
* @param beanManager
*/
public CDIBeanResolver(BeanManager beanManager) {
this(beanManager, CDI_BEAN_RESOLVER_PRIORITY);
}
/**
*
* @param beanManager
* @param priority
*/
public CDIBeanResolver(BeanManager beanManager, int priority) {
super(priority);
this.beanManager = beanManager;
}
@Override
public Object resolve(Object contextObject, String name,
ResolutionContext context) {
if (contextObject != null) {
return null;
}
Optional<Bean<?>> bean = beanCache.get(name);
if (!bean.isPresent()) {
// Unsuccessful lookup already performed
return null;
}
return getReference(bean.get(), context);
}
@Override
public void init() {
if (beanManager == null) {
beanManager = BeanManagerLocator.locate();
}
if (beanManager == null) {
throw new IllegalStateException(
"BeanManager not set - invalid resolver configuration");
}
// Init cache max size
long beanCacheMaxSize = configuration
.getLongPropertyValue(BEAN_CACHE_MAX_SIZE_KEY);
beanCache = configuration.getComputingCacheFactory().create(
COMPUTING_CACHE_CONSUMER_ID,
key -> {
Set<Bean<?>> beans = beanManager.getBeans(key);
// Check required for CDI 1.0
if (beans == null || beans.isEmpty()) {
return Optional.empty();
}
try {
return Optional.of(beanManager.resolve(beans));
} catch (AmbiguousResolutionException e) {
LOGGER.warn(
"An ambiguous EL name exists [name: {}]",
key);
return Optional.empty();
}
}, null, beanCacheMaxSize, null);
LOGGER.debug("Initialized [beanCacheMaxSize: {}]", beanCacheMaxSize);
}
@Override
public Set<ConfigurationKey> getConfigurationKeys() {
return Collections.singleton(BEAN_CACHE_MAX_SIZE_KEY);
}
private <T> Object getReference(Bean<T> bean, ResolutionContext context) {
CreationalContext<T> creationalContext = beanManager
.createCreationalContext(bean);
if (Dependent.class.equals(bean.getScope())) {
T reference = bean.create(creationalContext);
context.registerReleaseCallback(new DependentDestroyCallback<>(
bean, creationalContext, reference));
return reference;
} else {
return beanManager.getReference(bean, Object.class,
creationalContext);
}
}
@Override
public Hint createHint(Object contextObject, String name,
ResolutionContext context) {
if (contextObject == null) {
Optional<Bean<?>> bean = beanCache.getIfPresent(name);
if (bean.isPresent()) {
return new CDIBeanHint(bean.get());
}
}
return Hints.INAPPLICABLE_HINT;
}
private static class DependentDestroyCallback<T> implements ReleaseCallback {
private final Bean<T> bean;
private final CreationalContext<T> creationalContext;
private final T instance;
private DependentDestroyCallback(Bean<T> bean,
CreationalContext<T> creationalContext, T instance) {
this.bean = bean;
this.creationalContext = creationalContext;
this.instance = instance;
}
@Override
public void release() {
bean.destroy(instance, creationalContext);
}
}
private class CDIBeanHint implements Hint {
private final Bean<?> bean;
/**
*
* @param bean
*/
public CDIBeanHint(Bean<?> bean) {
this.bean = bean;
}
@Override
public Object resolve(Object contextObject, String name,
ResolutionContext context) {
if (contextObject != null) {
return null;
}
return CDIBeanResolver.this.getReference(bean, context);
}
}
}