package org.hotswap.agent.plugin.weld.command;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.enterprise.context.SessionScoped;
import javax.enterprise.context.spi.Context;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanAttributes;
import javax.enterprise.inject.spi.CDI;
import javax.servlet.http.HttpSession;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.plugin.weld.BeanReloadStrategy;
import org.hotswap.agent.plugin.weld.WeldClassSignatureHelper;
import org.hotswap.agent.plugin.weld.beans.ContextualReloadHelper;
import org.hotswap.agent.util.ReflectionHelper;
import org.jboss.weld.annotated.enhanced.EnhancedAnnotatedType;
import org.jboss.weld.annotated.enhanced.jlr.EnhancedAnnotatedTypeImpl;
import org.jboss.weld.annotated.slim.SlimAnnotatedType;
import org.jboss.weld.bean.AbstractClassBean;
import org.jboss.weld.bean.ManagedBean;
import org.jboss.weld.bean.attributes.BeanAttributesFactory;
import org.jboss.weld.bean.builtin.BeanManagerProxy;
import org.jboss.weld.context.AbstractBoundContext;
import org.jboss.weld.context.PassivatingContextWrapper;
import org.jboss.weld.context.beanstore.BeanStore;
import org.jboss.weld.context.beanstore.BoundBeanStore;
import org.jboss.weld.context.beanstore.NamingScheme;
import org.jboss.weld.context.beanstore.http.EagerSessionBeanStore;
import org.jboss.weld.context.http.HttpSessionContextImpl;
import org.jboss.weld.manager.BeanManagerImpl;
import org.jboss.weld.metadata.TypeStore;
import org.jboss.weld.resources.ClassTransformer;
import org.jboss.weld.resources.ReflectionCache;
import org.jboss.weld.resources.ReflectionCacheFactory;
import org.jboss.weld.resources.SharedObjectCache;
import org.jboss.weld.util.Beans;
public class BeanReloadExecutor {
private static AgentLogger LOGGER = AgentLogger.getLogger(BeanReloadExecutor.class);
/**
* Reload bean in existing bean manager.
* @param reloadStrategy
* @param oldSignatureByStrategy
*
* @param bdaId the Bean Deployment Archive ID
* @param beanClassName
*/
@SuppressWarnings("rawtypes")
public static void reloadBean(String bdaId, Class<?> beanClass, String oldSignatureByStrategy, String strReloadStrategy) {
// check if it is Object descendant (not interface)
if (!Object.class.isAssignableFrom(beanClass)) {
return;
}
BeanReloadStrategy reloadStrategy;
try {
reloadStrategy = BeanReloadStrategy.valueOf(strReloadStrategy);
} catch (Exception e) {
reloadStrategy = BeanReloadStrategy.NEVER;
}
BeanManagerImpl beanManager;
if (CDI.current().getBeanManager() instanceof BeanManagerImpl) {
beanManager = ((BeanManagerImpl) CDI.current().getBeanManager()).unwrap();
} else {
beanManager = ((BeanManagerProxy) CDI.current().getBeanManager()).unwrap();
}
Set<Bean<?>> beans = beanManager.getBeans(beanClass);
if (beans != null && !beans.isEmpty()) {
for (Bean<?> bean : beans) {
if (bean instanceof AbstractClassBean) {
doReloadAbstractClassBean(beanManager, bdaId, beanClass, (AbstractClassBean) bean, oldSignatureByStrategy, reloadStrategy);
} else {
LOGGER.warning("reloadBean() : class '{}' reloading is not implemented ({}).",
bean.getClass().getName(), bean.getBeanClass());
}
}
LOGGER.debug("Bean reloaded '{}'", beanClass.getName());
} else {
try {
ClassTransformer classTransformer = getClassTransformer();
SlimAnnotatedType<?> annotatedType = classTransformer.getBackedAnnotatedType(beanClass, bdaId);
boolean managedBeanOrDecorator = Beans.isTypeManagedBeanOrDecoratorOrInterceptor(annotatedType);
if (managedBeanOrDecorator) {
EnhancedAnnotatedType eat = EnhancedAnnotatedTypeImpl.of(annotatedType, classTransformer);
defineManagedBean(beanManager, eat);
// define managed bean
// beanManager.cleanupAfterBoot();
LOGGER.debug("Bean defined '{}'", beanClass.getName());
} else {
// TODO : define session bean
LOGGER.warning("Bean NOT? defined '{}', session bean?", beanClass.getName());
}
} catch (Exception e) {
LOGGER.debug("Bean definition failed", e);
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void doReloadAbstractClassBean(BeanManagerImpl beanManager, String bdaId, Class<?> beanClass,
AbstractClassBean bean, String oldSignatureByStrategy, BeanReloadStrategy reloadStrategy) {
EnhancedAnnotatedType eat = createAnnotatedTypeForExistingBeanClass(bdaId, beanClass);
if (!eat.isAbstract() || !eat.getJavaClass().isInterface()) { // injectionTargetCannotBeCreatedForInterface
bean.setProducer(beanManager.getLocalInjectionTargetFactory(eat).createInjectionTarget(eat, bean, false));
String signatureByStrategy = WeldClassSignatureHelper.getSignatureByStrategy(reloadStrategy, beanClass);
if (bean instanceof ManagedBean && (
reloadStrategy == BeanReloadStrategy.CLASS_CHANGE ||
(reloadStrategy != BeanReloadStrategy.NEVER && signatureByStrategy != null && !signatureByStrategy.equals(oldSignatureByStrategy)))
) {
// Reload bean in contexts - invalidates existing instances
doReloadManagedBeanInContexts(beanManager, beanClass, (ManagedBean) bean);
} else {
// Reinjects bean instances in aproperiate contexts
doReinjectAbstractClassBeanInstances(beanManager, beanClass, bean);
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void doReinjectAbstractClassBeanInstances(BeanManagerImpl beanManager, Class<?> beanClass, AbstractClassBean bean) {
// keep beans in contexts, reinitialize bean injection points
try {
Map<Class<? extends Annotation>, List<Context>> contexts = getContexts(beanManager);
List<Context> ctx = contexts.get(bean.getScope());
if (ctx != null) {
for (Context context : ctx) {
if (context.isActive()) {
Object get = context.get(bean);
if (get != null) {
LOGGER.debug("Bean injection points are reinitialized '{}'", beanClass.getName());
bean.getProducer().inject(get, beanManager.createCreationalContext(bean));
}
} else {
context = PassivatingContextWrapper.unwrap(context);
if (context.getScope().equals(SessionScoped.class) && context instanceof HttpSessionContextImpl) {
doReinjectInSessionCtx(beanManager, beanClass, bean, context);
}
}
}
}
} catch (org.jboss.weld.context.ContextNotActiveException e) {
LOGGER.warning("No active contexts for {}", beanClass.getName());
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void doReinjectInSessionCtx(BeanManagerImpl beanManager, Class<?> beanClass, AbstractClassBean bean, Context context) {
HttpSessionContextImpl sessionContext = (HttpSessionContextImpl) context;
List<HttpSession> seenSessions = HttpSessionsRegistry.getSeenSessions();
for (HttpSession session : seenSessions) {
BeanStore beanStore = null;
try {
NamingScheme namingScheme = (NamingScheme) ReflectionHelper.get(sessionContext, "namingScheme");
beanStore = new EagerSessionBeanStore(namingScheme, session);
ReflectionHelper.invoke(sessionContext, AbstractBoundContext.class, "setBeanStore", new Class[] {BoundBeanStore.class}, beanStore);
sessionContext.activate();
Object get = sessionContext.get(bean);
if (get != null) {
LOGGER.debug("Bean injection points are reinitialized '{}'", beanClass.getName());
bean.getProducer().inject(get, beanManager.createCreationalContext(bean));
}
} finally {
try {
sessionContext.deactivate();
} catch (Exception e) {
}
if (beanStore != null) {
try {
ReflectionHelper.invoke(sessionContext, AbstractBoundContext.class, "setBeanStore", new Class[] {BeanStore.class}, (BeanStore) null);
} catch (Exception e) {
}
}
}
}
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static void doReloadManagedBeanInContexts(BeanManagerImpl beanManager, Class<?> beanClass, ManagedBean managedBean) {
try {
Map<Class<? extends Annotation>, List<Context>> contexts = getContexts(beanManager);
List<Context> ctxList = contexts.get(managedBean.getScope());
if (ctxList != null) {
for(Context context: ctxList) {
if (context != null) {
LOGGER.debug("Inspecting context '{}' for bean class {}", context.getClass(), managedBean.getScope());
if(ContextualReloadHelper.addToReloadSet(context, managedBean)) {
LOGGER.debug("Bean {}, added to reload set in context {}", managedBean, context.getClass());
} else {
// fallback for not patched contexts
doReinjectAbstractClassBeanInstances(beanManager, beanClass, managedBean);
}
} else {
LOGGER.debug("No active contexts for bean: {} in scope: {}", managedBean.getScope(), beanClass.getName());
}
}
} else {
LOGGER.debug("No active contexts for bean: {} in scope: {}", managedBean.getScope(), beanClass.getName());
}
} catch (org.jboss.weld.context.ContextNotActiveException e) {
LOGGER.warning("No active contexts for {}", e, beanClass.getName());
} catch (Exception e) {
LOGGER.warning("Context for {} failed to reload", e, beanClass.getName());
}
}
@SuppressWarnings("unchecked")
private static Map<Class<? extends Annotation>, List<Context>> getContexts(BeanManagerImpl beanManager){
try {
return Map.class.cast(ReflectionHelper.get(beanManager, "contexts"));
} catch (Exception e) {
LOGGER.warning("BeanManagerImpl.contexts not accessible", e);
}
return Collections.emptyMap();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private static void defineManagedBean(BeanManagerImpl beanManager, EnhancedAnnotatedType eat) throws Exception {
BeanAttributes attributes = BeanAttributesFactory.forBean(eat, beanManager);
ManagedBean<?> bean = ManagedBean.of(attributes, eat, beanManager);
Field field = beanManager.getClass().getDeclaredField("beanSet");
field.setAccessible(true);
field.set(beanManager, Collections.synchronizedSet(new HashSet<Bean<?>>()));
// TODO:
beanManager.addBean(bean);
beanManager.getBeanResolver().clear();
bean.initializeAfterBeanDiscovery();
}
private static EnhancedAnnotatedType<?> createAnnotatedTypeForExistingBeanClass(String bdaId, Class<?> beanClass) {
ClassTransformer classTransformer = getClassTransformer();
SlimAnnotatedType<?> annotatedType = classTransformer.getBackedAnnotatedType(beanClass, bdaId);
return EnhancedAnnotatedTypeImpl.of(annotatedType, classTransformer);
}
private static ClassTransformer getClassTransformer() {
TypeStore store = new TypeStore();
SharedObjectCache cache = new SharedObjectCache();
ReflectionCache reflectionCache = ReflectionCacheFactory.newInstance(store);
ClassTransformer classTransformer = new ClassTransformer(store, cache, reflectionCache, "STATIC_INSTANCE");
return classTransformer;
}
}