package org.hotswap.agent.annotation.handler; import org.hotswap.agent.config.ClassLoaderInitListener; import org.hotswap.agent.config.PluginManager; import org.hotswap.agent.annotation.Init; import org.hotswap.agent.command.Scheduler; import org.hotswap.agent.config.PluginConfiguration; import org.hotswap.agent.logging.AgentLogger; import org.hotswap.agent.util.HotswapTransformer; import org.hotswap.agent.watch.Watcher; import java.lang.instrument.Instrumentation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.List; /** * Annotation handler - handle @Init annotation on fields/methods. * <p/> * The {org.hotswap.agent.annotation.Init} annotation can be set on field or method and static or non static. * See the annotation description for usage info. * * @author Jiri Bubnik * @see org.hotswap.agent.annotation.Init */ public class InitHandler implements PluginHandler<Init> { private static AgentLogger LOGGER = AgentLogger.getLogger(InitHandler.class); protected PluginManager pluginManager; public InitHandler(PluginManager pluginManager) { this.pluginManager = pluginManager; } @Override public boolean initField(PluginAnnotation pluginAnnotation) { Field field = pluginAnnotation.getField(); // if plugin not set, it is static method on plugin registration, use agent classloader ClassLoader appClassLoader = pluginAnnotation.getPlugin() == null ? getClass().getClassLoader() : pluginManager.getPluginRegistry().getAppClassLoader(pluginAnnotation.getPlugin()); Object value = resolveType(appClassLoader, pluginAnnotation.getPluginClass(), field.getType()); field.setAccessible(true); try { field.set(pluginAnnotation.getPlugin(), value); } catch (IllegalAccessException e) { LOGGER.error("Unable to set plugin field '{}' to value '{}' on plugin '{}'", e, field.getName(), value, pluginAnnotation.getPluginClass()); return false; } return true; } // Main plugin initialization via @Init method. // If static, just register callback, otherwise the method is immediately invoked. @Override public boolean initMethod(PluginAnnotation pluginAnnotation) { Object plugin = pluginAnnotation.getPlugin(); if (plugin == null) { // special case - static method - register callback if (Modifier.isStatic(pluginAnnotation.getMethod().getModifiers())) return registerClassLoaderInit(pluginAnnotation); else return true; } else { if (!Modifier.isStatic(pluginAnnotation.getMethod().getModifiers())) { ClassLoader appClassLoader = pluginManager.getPluginRegistry().getAppClassLoader(plugin); return invokeInitMethod(pluginAnnotation, plugin, appClassLoader); } else return true; } } // resolve all method parameter types to actual values and invoke the plugin method (both static and non static) private boolean invokeInitMethod(PluginAnnotation pluginAnnotation, Object plugin, ClassLoader classLoader) { List<Object> args = new ArrayList<Object>(); for (Class type : pluginAnnotation.getMethod().getParameterTypes()) { args.add(resolveType(classLoader, pluginAnnotation.getPluginClass(), type)); } try { pluginAnnotation.getMethod().invoke(plugin, args.toArray()); return true; } catch (IllegalAccessException e) { LOGGER.error("IllegalAccessException in init method on plugin {}.", e, pluginAnnotation.getPluginClass()); return false; } catch (InvocationTargetException e) { LOGGER.error("InvocationTargetException in init method on plugin {}.", e, pluginAnnotation.getPluginClass()); return false; } } /** * Register on classloader init event - call the @Init static method. * * @param pluginAnnotation description of plugin method to call * @return true if ok */ protected boolean registerClassLoaderInit(final PluginAnnotation pluginAnnotation) { LOGGER.debug("Registering ClassLoaderInitListener on {}", pluginAnnotation.getPluginClass()); pluginManager.registerClassLoaderInitListener(new ClassLoaderInitListener() { @Override public void onInit(ClassLoader classLoader) { // call the init method LOGGER.debug("Init plugin {} at classloader {}.", pluginAnnotation.getPluginClass(), classLoader); invokeInitMethod(pluginAnnotation, null, classLoader); } }); return true; } /** * Support for autowiring of agent services - resolve instance by class. * * @param classLoader application classloader * @param pluginClass used only for debugging messages * @param type requested type * @return resolved instance or null (error is logged) */ @SuppressWarnings("unchecked") protected Object resolveType(ClassLoader classLoader, Class pluginClass, Class type) { if (type.isAssignableFrom(PluginManager.class)) { return pluginManager; } else if (type.isAssignableFrom(Watcher.class)) { return pluginManager.getWatcher(); } else if (type.isAssignableFrom(Scheduler.class)) { return pluginManager.getScheduler(); } else if (type.isAssignableFrom(HotswapTransformer.class)) { return pluginManager.getHotswapTransformer(); } else if (type.isAssignableFrom(PluginConfiguration.class)) { return pluginManager.getPluginConfiguration(classLoader); } else if (type.isAssignableFrom(ClassLoader.class)) { return classLoader; } else if (type.isAssignableFrom(Instrumentation.class)) { return pluginManager.getInstrumentation(); } else { LOGGER.error("Unable process @Init on plugin '{}'." + " Type '" + type + "' is not recognized for @Init annotation.", pluginClass); return null; } } }