package org.hotswap.agent.config; import org.hotswap.agent.command.Scheduler; import org.hotswap.agent.command.impl.SchedulerImpl; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.util.HotswapTransformer; import org.hotswap.agent.util.classloader.ClassLoaderDefineClassPatcher; import org.hotswap.agent.util.classloader.ClassLoaderPatcher; import org.hotswap.agent.watch.Watcher; import org.hotswap.agent.watch.WatcherFactory; import java.io.IOException; import java.lang.instrument.ClassDefinition; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.*; /** * The main agent plugin manager, well known singleton controller. * * @author Jiri Bubnik */ public class PluginManager { private static AgentLogger LOGGER = AgentLogger.getLogger(PluginManager.class); public static final String PLUGIN_PACKAGE = "org.hotswap.agent.plugin"; ////////////////////////// MANAGER SINGLETON ///////////////////////////////////// // singleton instance private static PluginManager INSTANCE = new PluginManager(); /** * Get the singleton instance of the plugin manager. */ public static PluginManager getInstance() { return INSTANCE; } // ensure singleton private PluginManager() { hotswapTransformer = new HotswapTransformer(); pluginRegistry = new PluginRegistry(this, classLoaderPatcher); } // the instrumentation API private Instrumentation instrumentation; private Object hotswapLock = new Object(); ////////////////////////// PLUGINS ///////////////////////////////////// /** * Returns a plugin instance by its type and classLoader. * * @param clazz type name of the plugin (IllegalArgumentException class is not known to the classLoader) * @param classLoader classloader of the plugin * @return plugin instance or null if not found */ public Object getPlugin(String clazz, ClassLoader classLoader) { try { return getPlugin(Class.forName(clazz), classLoader); } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Plugin class not found " + clazz, e); } } /** * Returns a plugin instance by its type and classLoader. * * @param clazz type of the plugin * @param classLoader classloader of the plugin * @param <T> type of the plugin to return correct instance. * @return the plugin or null if not found. */ public <T> T getPlugin(Class<T> clazz, ClassLoader classLoader) { return pluginRegistry.getPlugin(clazz, classLoader); } /** * Check if plugin is initialized in classLoader. * * @param pluginClass type of the plugin * @param classLoader classloader of the plugin * @param checkParent for parent classloaders as well? * @return true/false */ public boolean isPluginInitialized(String pluginClassName, ClassLoader classLoader) { Class<Object> pluginClass = pluginRegistry.getPluginClass(pluginClassName); return pluginClass != null && pluginRegistry.hasPlugin(pluginClass, classLoader, false); } /** * Initialize the singleton plugin manager. * <ul> * <li>Create new resource watcher using WatcherFactory and start it in separate thread.</li> * <li>Create new scheduler and start it in separate thread.</li> * <li>Scan for plugins</li> * <li>Register HotswapTransformer with the javaagent instrumentation class</li> * </ul> * * @param instrumentation javaagent instrumentation. */ public void init(Instrumentation instrumentation) { this.instrumentation = instrumentation; // create default configuration from this classloader ClassLoader classLoader = getClass().getClassLoader(); classLoaderConfigurations.put(classLoader, new PluginConfiguration(classLoader)); if (watcher == null) { try { watcher = new WatcherFactory().getWatcher(); } catch (IOException e) { LOGGER.debug("Unable to create default watcher.", e); } } watcher.run(); if (scheduler == null) { scheduler = new SchedulerImpl(); } scheduler.run(); pluginRegistry.scanPlugins(getClass().getClassLoader(), PLUGIN_PACKAGE); LOGGER.debug("Registering transformer "); instrumentation.addTransformer(hotswapTransformer); } ClassLoaderPatcher classLoaderPatcher = new ClassLoaderDefineClassPatcher(); Map<ClassLoader, PluginConfiguration> classLoaderConfigurations = new HashMap<ClassLoader, PluginConfiguration>(); Set<ClassLoaderInitListener> classLoaderInitListeners = new HashSet<ClassLoaderInitListener>(); public void registerClassLoaderInitListener(ClassLoaderInitListener classLoaderInitListener) { classLoaderInitListeners.add(classLoaderInitListener); // call init on this classloader immediately, because it is already initialized classLoaderInitListener.onInit(getClass().getClassLoader()); } public void initClassLoader(ClassLoader classLoader) { // use default protection domain initClassLoader(classLoader, classLoader.getClass().getProtectionDomain()); } public void initClassLoader(ClassLoader classLoader, ProtectionDomain protectionDomain) { if (classLoaderConfigurations.containsKey(classLoader)) return; // parent of current classloader (system/bootstrap) if (getClass().getClassLoader() != null && classLoader != null && classLoader.equals(getClass().getClassLoader().getParent())) return; // synchronize ClassLoader patching - multiple classloaders may be patched at the same time // and they may synchronize loading for security reasons and introduce deadlocks synchronized (this) { if (classLoaderConfigurations.containsKey(classLoader)) return; // transformation if (classLoader != null && classLoaderPatcher.isPatchAvailable(classLoader)) { classLoaderPatcher.patch(getClass().getClassLoader(), PLUGIN_PACKAGE.replace(".", "/"), classLoader, protectionDomain); } // create new configuration for the classloader PluginConfiguration configuration = new PluginConfiguration(getPluginConfiguration(getClass().getClassLoader()), classLoader); classLoaderConfigurations.put(classLoader, configuration); } // call listeners for (ClassLoaderInitListener classLoaderInitListener : classLoaderInitListeners) classLoaderInitListener.onInit(classLoader); } /** * Remove any classloader reference and close all plugin instances associated with classloader. * This method is called typically after webapp undeploy. * * @param classLoader the classloader to cleanup */ public void closeClassLoader(ClassLoader classLoader) { pluginRegistry.closeClassLoader(classLoader); classLoaderConfigurations.remove(classLoader); hotswapTransformer.closeClassLoader(classLoader); } public PluginConfiguration getPluginConfiguration(ClassLoader classLoader) { // if needed, iterate to first parent loader with a known configuration ClassLoader loader = classLoader; while (loader != null && !classLoaderConfigurations.containsKey(loader)) loader = loader.getParent(); return classLoaderConfigurations.get(loader); } ////////////////////////// AGENT SERVICES ///////////////////////////////////// private PluginRegistry pluginRegistry; /** * Returns the plugin registry service. */ public PluginRegistry getPluginRegistry() { return pluginRegistry; } /** * Sets the plugin registry service. */ public void setPluginRegistry(PluginRegistry pluginRegistry) { this.pluginRegistry = pluginRegistry; } protected HotswapTransformer hotswapTransformer; /** * Returns the hotswap transformer service. */ public HotswapTransformer getHotswapTransformer() { return hotswapTransformer; } protected Watcher watcher; /** * Returns the watcher service. */ public Watcher getWatcher() { return watcher; } protected Scheduler scheduler; /** * Returns the scheduler service. */ public Scheduler getScheduler() { return scheduler; } /** * Redefine the supplied set of classes using the supplied bytecode. * * This method operates on a set in order to allow interdependent changes to more than one class at the same time * (a redefinition of class A can require a redefinition of class B). * * @param reloadMap class -> new bytecode * @see java.lang.instrument.Instrumentation#redefineClasses(java.lang.instrument.ClassDefinition...) */ public void hotswap(Map<Class<?>, byte[]> reloadMap) { if (instrumentation == null) { throw new IllegalStateException("Plugin manager is not correctly initialized - no instrumentation available."); } synchronized (reloadMap) { ClassDefinition[] definitions = new ClassDefinition[reloadMap.size()]; String[] classNames = new String[reloadMap.size()]; int i = 0; for (Map.Entry<Class<?>, byte[]> entry : reloadMap.entrySet()) { classNames[i] = entry.getKey().getName(); definitions[i++] = new ClassDefinition(entry.getKey(), entry.getValue()); } try { LOGGER.reload("Reloading classes {} (autoHotswap)", Arrays.toString(classNames)); synchronized (hotswapLock) { instrumentation.redefineClasses(definitions); } LOGGER.debug("... reloaded classes {} (autoHotswap)", Arrays.toString(classNames)); } catch (Exception e) { throw new IllegalStateException("Unable to redefine classes", e); } reloadMap.clear(); } } /** * @return the instrumentation */ public Instrumentation getInstrumentation() { return instrumentation; } }