package org.hotswap.agent.util;
import org.hotswap.agent.config.PluginManager;
import java.lang.reflect.Method;
/**
* Invoke methods on plugin manager, avoid classloader conflicts.
* Each method has two variants - direct call or method source code builder.
* <p/>
* Note that because methods are invoked accross classloader, only parameters known to both classloaders
* can be used. This is generally true for basic and java.* types.
*
* @author Jiri Bubnik
*/
public class PluginManagerInvoker {
/**
* Initialize plugin for a classloader.
*
* @param pluginClass identify plugin instance
* @param appClassLoader classloader in which the plugin should reside
*/
public static <T> T callInitializePlugin(Class<T> pluginClass, ClassLoader appClassLoader) {
// noinspection unchecked
return (T) PluginManager.getInstance().getPluginRegistry().initializePlugin(
pluginClass.getName(), appClassLoader
);
}
public static String buildInitializePlugin(Class pluginClass) {
return buildInitializePlugin(pluginClass, "getClass().getClassLoader()");
}
public static String buildInitializePlugin(Class pluginClass, String classLoaderVar) {
return "org.hotswap.agent.config.PluginManager.getInstance().getPluginRegistry().initializePlugin(" +
"\"" + pluginClass.getName() + "\", " + classLoaderVar +
");";
}
/**
* Free all classloader references and close any associated plugin instance.
* Typical use is after webapp undeploy.
*
* @param appClassLoader clasloade to free
*/
public static void callCloseClassLoader(ClassLoader appClassLoader) {
PluginManager.getInstance().closeClassLoader(appClassLoader);
}
public static String buildCallCloseClassLoader(String classLoaderVar) {
return "org.hotswap.agent.config.PluginManager.getInstance().closeClassLoader(" + classLoaderVar + ");";
}
/**
* Methods on plugin should be called via reflection, because the real plugin object is in parent classloader,
* but plugin class may be defined in app classloader as well introducing ClassCastException on same class name.
*
* @param pluginClass class name of the plugin - it is used to resolve plugin instance from plugin manager
* @param appClassLoader application classloader (to resolve plugin instance)
* @param method method name
* @param paramTypes param types (as required by reflection)
* @param params actual param values
* @return method return value
*/
public static Object callPluginMethod(Class pluginClass, ClassLoader appClassLoader, String method, Class[] paramTypes, Object[] params) {
Object pluginInstance = PluginManager.getInstance().getPlugin(pluginClass.getName(), appClassLoader);
try {
Method m = pluginInstance.getClass().getDeclaredMethod(method, paramTypes);
return m.invoke(pluginInstance, params);
} catch (Exception e) {
throw new Error(String.format("Exception calling method %s on plugin class %s", method, pluginClass), e);
}
}
/**
* Equivalent to callPluginMethod for insertion into source code.
* <p/>
* PluginManagerInvoker.buildCallPluginMethod(this, "hibernateInitialized",
* "getClass().getClassLoader()", "java.lang.ClassLoader")
*
* @param pluginClass plugin to use
* @param method method name
* @param paramValueAndType for each param its value AND type must be provided
* @return method source code
*/
public static String buildCallPluginMethod(Class pluginClass, String method, String... paramValueAndType) {
return buildCallPluginMethod("getClass().getClassLoader()", pluginClass, method, paramValueAndType);
}
/**
* Same as {@link PluginManagerInvoker#buildCallPluginMethod(Class, String, String...)}, but with explicit
* appClassLoader variable. Use this method if appClassLoader is different from getClass().getClassLoader().
*/
public static String buildCallPluginMethod(String appClassLoaderVar, Class pluginClass,
String method, String... paramValueAndType) {
String managerClass = PluginManager.class.getName();
int paramCount = paramValueAndType.length / 2;
StringBuilder b = new StringBuilder();
// block to hide variables and catch checked exceptions
b.append("try {");
b.append("ClassLoader __pluginClassLoader = ");
b.append(managerClass);
b.append(".class.getClassLoader();");
// Object __pluginInstance = org.hotswap.agent.config.PluginManager.getInstance().getPlugin(org.hotswap.agent.plugin.TestPlugin.class.getName(), __pluginClassLoader);
b.append("Object __pluginInstance = ");
b.append(managerClass);
b.append(".getInstance().getPlugin(");
b.append(pluginClass.getName());
b.append(".class.getName(), " + appClassLoaderVar + ");");
// Class __pluginClass = __pluginClassLoader.loadClass("org.hotswap.agent.plugin.TestPlugin");
b.append("Class __pluginClass = ");
b.append("__pluginClassLoader.loadClass(\"");
b.append(pluginClass.getName());
b.append("\");");
// param types
b.append("Class[] paramTypes = new Class[" + paramCount + "];");
for (int i = 0; i < paramCount; i++) {
// paramTypes[i] = = __pluginClassLoader.loadClass("my.test.TestClass").getClass();
b.append("paramTypes[" + i + "] = __pluginClassLoader.loadClass(\"" + paramValueAndType[(i * 2) + 1] + "\");");
}
// java.lang.reflect.Method __pluginMethod = __pluginClass.getDeclaredMethod("method", paramType1, paramType2);
b.append("java.lang.reflect.Method __callPlugin = __pluginClass.getDeclaredMethod(\"");
b.append(method);
b.append("\", paramTypes");
b.append(");");
b.append("Object[] params = new Object[" + paramCount + "];");
for (int i = 0; i < paramCount; i = i + 1) {
b.append("params[" + i + "] = " + paramValueAndType[i * 2] + ";");
}
// __pluginMethod.invoke(__pluginInstance, param1, param2);
b.append("__callPlugin.invoke(__pluginInstance, params);");
// catch (Exception e) {throw new Error(e);}
b.append("} catch (Exception e) {throw new Error(e);}");
return b.toString();
}
}