package org.hotswap.agent.plugin.weld.command; import java.io.File; import java.io.IOException; import java.lang.reflect.Type; import java.util.Collection; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.CDI; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.plugin.weld.WeldClassSignatureHelper; import org.hotswap.agent.plugin.weld.WeldPlugin; import org.hotswap.agent.util.PluginManagerInvoker; import org.hotswap.agent.util.ReflectionHelper; import org.jboss.weld.bean.builtin.BeanManagerProxy; import org.jboss.weld.bootstrap.spi.BeanDeploymentArchive; import org.jboss.weld.bootstrap.spi.BeansXml; import org.jboss.weld.manager.BeanManagerImpl; /** * Handles creating and redefinition of bean classes in BeanDeploymentArchive * * @author Vladimir Dvorak * @author alpapad@gmail.com */ public class BeanDeploymentArchiveAgent { private static AgentLogger LOGGER = AgentLogger.getLogger(BeanDeploymentArchiveAgent.class); /** * Flag to check the reload status. In unit test we need to wait for reload * finishing before the test can continue. Set flag to true in the test class * and wait until the flag is false again. */ public static boolean reloadFlag = false; private BeanDeploymentArchive deploymentArchive; private String archivePath; private boolean registered = false; /** * Register bean archive into BdaAgentRegistry and into WeldPlugin. Current classLoader is set to * beanArchive classLoader. * * @param appClassLoader the class loader - container or application class loader. * @param beanArchive the bean archive to be registered * @param beanArchiveType the bean archive type */ public static void registerArchive(ClassLoader appClassLoader, BeanDeploymentArchive beanArchive, String beanArchiveType) { BeansXml beansXml = beanArchive.getBeansXml(); if (beansXml != null && beansXml.getUrl() != null && (beanArchiveType == null || "EXPLICIT".equals(beanArchiveType) || "IMPLICIT".equals(beanArchiveType))) { String archivePath = null; String beansXmlPath = beansXml.getUrl().getPath(); if (beansXmlPath.endsWith("META-INF/beans.xml")) { archivePath = beansXmlPath.substring(0, beansXmlPath.length() - "META-INF/beans.xml".length()); } else if (beansXmlPath.endsWith("WEB-INF/beans.xml")) { archivePath = beansXmlPath.substring(0, beansXmlPath.length() - "beans.xml".length()) + "classes"; } if (archivePath.endsWith(".jar!/")) { archivePath = archivePath.substring(0, archivePath.length() - "!/".length()); } BeanDeploymentArchiveAgent bdaAgent = null; try { LOGGER.debug("BeanDeploymentArchiveAgent registerArchive bdaId='{}' archivePath='{}'.", beanArchive.getId(), archivePath); // check that it is regular file // toString() is weird and solves HiearchicalUriException for URI like "file:./src/resources/file.txt". @SuppressWarnings("unused") File path = new File(archivePath); Class<?> registryClass = Class.forName(BdaAgentRegistry.class.getName(), true, appClassLoader); boolean contain = (boolean) ReflectionHelper.invoke(null, registryClass, "contains", new Class[] {String.class}, archivePath); if (!contain) { bdaAgent = new BeanDeploymentArchiveAgent(beanArchive, archivePath); ReflectionHelper.invoke(null, registryClass, "put", new Class[] {String.class, BeanDeploymentArchiveAgent.class}, archivePath, bdaAgent); bdaAgent.register(); } } catch (IllegalArgumentException e) { LOGGER.warning("Unable to watch BeanDeploymentArchive with id={}", beanArchive.getId()); } catch (Exception e) { LOGGER.error("Register archive failed.", e.getMessage()); } } else { // TODO: } } private void register() { if (!registered) { registered = true; PluginManagerInvoker.callPluginMethod(WeldPlugin.class, getClass().getClassLoader(), "registerBeanDeplArchivePath", new Class[] { String.class }, new Object[] { archivePath }); } } /** * Gets the collection of registered BeanDeploymentArchive(s) * * @return the instances */ public static Collection<BeanDeploymentArchiveAgent> getInstances() { return BdaAgentRegistry.values(); } private BeanDeploymentArchiveAgent(BeanDeploymentArchive deploymentArchive, String archivePath) { this.deploymentArchive = deploymentArchive; this.archivePath = archivePath; } /** * Gets the Bean depoyment ID - bdaId. * * @return the bdaId */ public String getBdaId() { return deploymentArchive.getId(); } /** * Gets the archive path. * * @return the archive path */ public String getArchivePath() { return archivePath; } /** * Gets the deployment archive. * * @return the deployment archive */ public BeanDeploymentArchive getDeploymentArchive() { return deploymentArchive; } /** * Reload bean (reload bean according strategy, reinject instances). Called from BeanClassRefreshCommand. * * @param classLoader * @param archivePath * @param beanClassName * @throws IOException error working with classDefinition */ public static void reloadBean(ClassLoader classLoader, String archivePath, String beanClassName, String oldSignatureByStrategy, String strReloadStrategy) throws IOException { BeanDeploymentArchiveAgent bdaAgent = BdaAgentRegistry.get(archivePath); if (bdaAgent == null) { LOGGER.error("Archive path '{}' is not associated with any BeanDeploymentArchiveAgent", archivePath); return; } ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); try { Thread.currentThread().setContextClassLoader(classLoader); // BDA classLoader can be different then appClassLoader for Wildfly/EAR deployment // therefore we use class loader from BdaAgent class which is classloader for BDA Class<?> beanClass = bdaAgent.getClass().getClassLoader().loadClass(beanClassName); BeanManagerImpl beanManager; if (CDI.current().getBeanManager() instanceof BeanManagerImpl) { beanManager = ((BeanManagerImpl) CDI.current().getBeanManager()).unwrap(); } else { beanManager = ((BeanManagerProxy) CDI.current().getBeanManager()).unwrap(); } ClassLoader beanManagerClassLoader = beanManager.getClass().getClassLoader(); Class<?> bdaAgentClazz = Class.forName(BeanReloadExecutor.class.getName(), true, beanManagerClassLoader); // Execute reload in BeanManagerClassLoader since reloading creates weld classes used for bean redefinition // (like EnhancedAnnotatedType) ReflectionHelper.invoke(null, bdaAgentClazz, "reloadBean", new Class[] {String.class, Class.class, String.class, String.class }, bdaAgent.getBdaId(), beanClass, oldSignatureByStrategy, strReloadStrategy ); } catch (Exception e) { LOGGER.error("Reload bean failed.", e); } finally { reloadFlag = false; Thread.currentThread().setContextClassLoader(oldContextClassLoader); } } /** * Recreate proxy classes, Called from BeanClassRefreshCommand. * * @param classLoader the class loader * @param archivePath the bean archive path * @param registeredProxiedBeans the registered proxied beans * @param beanClassName the bean class name * @param oldSignatureForProxyCheck the old signature for proxy check * @throws IOException error working with classDefinition */ @SuppressWarnings({ "unchecked", "rawtypes" }) public static void recreateProxy(ClassLoader classLoader, String archivePath, Map registeredProxiedBeans, String beanClassName, String oldSignatureForProxyCheck) throws IOException { BeanDeploymentArchiveAgent bdaAgent = BdaAgentRegistry.get(archivePath); if (bdaAgent == null) { LOGGER.error("Archive path '{}' is not associated with any BeanDeploymentArchiveAgent", archivePath); return; } try { // BDA classLoader can be different then appClassLoader for Wildfly/EAR deployment // therefore we use class loader from BdaAgent class which is classloader for BDA Class<?> beanClass = bdaAgent.getClass().getClassLoader().loadClass(beanClassName); bdaAgent.doRecreateProxy(classLoader, registeredProxiedBeans, beanClass, oldSignatureForProxyCheck); } catch (ClassNotFoundException e) { LOGGER.error("Bean class not found.", e); } } private void doRecreateProxy(ClassLoader classLoader, Map<Object, Object> registeredProxiedBeans, Class<?> beanClass, String oldClassSignature) { if (oldClassSignature != null && registeredProxiedBeans != null) { String newClassSignature = WeldClassSignatureHelper.getSignatureForProxyClass(beanClass); if (newClassSignature != null && !newClassSignature.equals(oldClassSignature)) { synchronized (registeredProxiedBeans) { if (!registeredProxiedBeans.isEmpty()) { doRecreateProxy(classLoader, registeredProxiedBeans, beanClass); } } } } } private void doRecreateProxy(ClassLoader classLoader, Map<Object, Object> registeredBeans, Class<?> proxyClass) { ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader(); try { ProxyClassLoadingDelegate.beginProxyRegeneration(); Class<?> proxyFactoryClass = null; for (Entry<Object, Object> entry : registeredBeans.entrySet()) { Bean<?> bean = (Bean<?>) entry.getKey(); if (bean != null) { Set<Type> types = bean.getTypes(); if (types.contains(proxyClass)) { Thread.currentThread().setContextClassLoader(bean.getBeanClass().getClassLoader()); if (proxyFactoryClass == null) { proxyFactoryClass = classLoader.loadClass("org.jboss.weld.bean.proxy.ProxyFactory"); } Object proxyFactory = entry.getValue(); LOGGER.info("Recreate proxyClass {} for bean class {}.", proxyClass.getName(), bean.getClass()); ReflectionHelper.invoke(proxyFactory, proxyFactoryClass, "getProxyClass", new Class[] {}); } } } } catch (Exception e) { LOGGER.error("recreateProxyFactory() exception {}.", e, e.getMessage()); } finally { Thread.currentThread().setContextClassLoader(oldContextClassLoader); ProxyClassLoadingDelegate.endProxyRegeneration(); } } }