package org.hotswap.agent.command;
import org.hotswap.agent.config.PluginManager;
import org.hotswap.agent.logging.AgentLogger;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Command to run in a target classloader. The command is always invoked via reflection.
*
* @author Jiri Bubnik
*/
public class ReflectionCommand extends MergeableCommand {
private static AgentLogger LOGGER = AgentLogger.getLogger(ReflectionCommand.class);
/**
* Run the method on target object.
*/
private Object target;
/**
* Run a method in the class - if null, run a method on this object, otherwise create new instance of className.
*/
private String className;
/**
* Method name to run.
*/
private String methodName;
/**
* Method actual parameters value. These parameter types must be known to the target classloader.
* For norma use (call application classloader from plugin class) this means that you can use only
* Java default types.
*/
private List<Object> params = new ArrayList<Object>();
/**
* Plugin object to resolve target classloader (if not set directly). May be null.
*/
private Object plugin;
/**
* Classloader application classloader to run the command in. If null, use agent classloader.
*/
private ClassLoader targetClassLoader;
/**
* Register a listener after the command is executed to obtain method invocation result.
*/
private CommandExecutionListener commandExecutionListener;
/**
* Define a command.
*/
public ReflectionCommand(Object plugin, String className, String methodName, ClassLoader targetClassLoader, Object... params) {
this.plugin = plugin;
this.className = className;
this.methodName = methodName;
this.targetClassLoader = targetClassLoader;
this.params = Arrays.asList(params);
}
/**
* Predefine a command. The params and/or target classloader will be set by setter.
*/
public ReflectionCommand(Object plugin, String className, String methodName) {
this.plugin = plugin;
this.className = className;
this.methodName = methodName;
}
/**
* Define a command on target object.
*/
public ReflectionCommand(Object target, String methodName, Object... params) {
this.target = target;
this.methodName = methodName;
this.params = Arrays.asList(params);
}
@Override
public String toString() {
return "Command{" +
"class='" + getClassName() + '\'' +
", methodName='" + getMethodName() + '\'' +
'}';
}
public String getClassName() {
if (className == null && target != null)
className = target.getClass().getName();
return className;
}
public String getMethodName() {
return methodName;
}
public List<Object> getParams() {
return params;
}
public ClassLoader getTargetClassLoader() {
if (targetClassLoader == null) {
if (target != null)
targetClassLoader = target.getClass().getClassLoader();
else
targetClassLoader = PluginManager.getInstance().getPluginRegistry().getAppClassLoader(plugin);
}
return targetClassLoader;
}
public void setTargetClassLoader(ClassLoader targetClassLoader) {
this.targetClassLoader = targetClassLoader;
}
public CommandExecutionListener getCommandExecutionListener() {
return commandExecutionListener;
}
public void setCommandExecutionListener(CommandExecutionListener commandExecutionListener) {
this.commandExecutionListener = commandExecutionListener;
}
/**
* Execute the command.
*/
public void executeCommand() {
// replace context classloader with application classloader
if (getTargetClassLoader() != null)
Thread.currentThread().setContextClassLoader(getTargetClassLoader());
ClassLoader targetClassLoader = Thread.currentThread().getContextClassLoader();
String className = getClassName();
String method = getMethodName();
List<Object> params = getParams();
Object result = null;
try {
result = doExecuteReflectionCommand(targetClassLoader, className, target, method, params);
} catch (ClassNotFoundException e) {
LOGGER.error("Class {} not found in classloader {}", e, className, targetClassLoader);
} catch (NoClassDefFoundError e) {
LOGGER.error("NoClassDefFoundError for class {} in classloader {}", e, className, targetClassLoader);
} catch (InstantiationException e) {
LOGGER.error("Unable instantiate class {} in classloader {}", e, className, targetClassLoader);
} catch (IllegalAccessException e) {
LOGGER.error("Method {} not public in class {}", e, method, className);
} catch (NoSuchMethodException e) {
LOGGER.error("Method {} not found in class {}", e, method, className);
} catch (InvocationTargetException e) {
LOGGER.error("Error executin method {} in class {}", e, method, className);
}
// notify lilstener
CommandExecutionListener listener = getCommandExecutionListener();
if (listener != null)
listener.commandExecuted(result);
}
protected Object doExecuteReflectionCommand(ClassLoader targetClassLoader, String className, Object target, String method, List<Object> params) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> classInAppClassLoader = Class.forName(className, true, targetClassLoader);
LOGGER.trace("Executing command: requestedClassLoader={}, resolvedClassLoader={}, class={}, method={}, params={}",
targetClassLoader, classInAppClassLoader.getClassLoader(), classInAppClassLoader, method, params);
Class[] paramTypes = new Class[params.size()];
int i = 0;
for (Object param : params) {
if (params == null)
throw new IllegalArgumentException("Cannot execute for null parameter value");
else {
paramTypes[i++] = param.getClass();
}
}
Method m = classInAppClassLoader.getDeclaredMethod(method, paramTypes);
return m.invoke(target, params.toArray());
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ReflectionCommand)) return false;
ReflectionCommand that = (ReflectionCommand) o;
if (!className.equals(that.className)) return false;
if (!methodName.equals(that.methodName)) return false;
if (!params.equals(that.params)) return false;
if (plugin != null ? !plugin.equals(that.plugin) : that.plugin != null) return false;
if (target != null ? !target.equals(that.target) : that.target != null) return false;
if (targetClassLoader != null ? !targetClassLoader.equals(that.targetClassLoader) : that.targetClassLoader != null) return false;
return true;
}
@Override
public int hashCode() {
int result = target != null ? target.hashCode() : 0;
result = 31 * result + className.hashCode();
result = 31 * result + methodName.hashCode();
result = 31 * result + params.hashCode();
result = 31 * result + (plugin != null ? plugin.hashCode() : 0);
result = 31 * result + (targetClassLoader != null ? targetClassLoader.hashCode() : 0);
return result;
}
}