package org.hotswap.agent.plugin.owb.command;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import org.hotswap.agent.annotation.FileEvent;
import org.hotswap.agent.command.Command;
import org.hotswap.agent.command.MergeableCommand;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.plugin.owb.BeanReloadStrategy;
import org.hotswap.agent.watch.WatchFileEvent;
/**
* BeanClassRefreshCommand. Collect all classes definitions/redefinitions for application
*
* 1. Merge all commands (definition, redefinition) for appClassLoader to single command.
* 2. Call proxy redefinitions in BeanArchiveAgent for all merged commands
* 3. Call bean class reload in BeanArchiveAgent for all merged commands
*
* @author Vladimir Dvorak
*/
public class BeanClassRefreshCommand extends MergeableCommand {
private static AgentLogger LOGGER = AgentLogger.getLogger(BeanClassRefreshCommand.class);
ClassLoader appClassLoader;
String className;
String classSignForProxyCheck;
String classSignByStrategy;
String strBeanReloadStrategy;
WatchFileEvent event;
/**
* Instantiates a new bean class refresh command.
*
* @param appClassLoader the application class loader
* @param className the class name
* @param classSignForProxyCheck the class signature for proxy check
* @param classSignByStrategy the class signature by strategy
* @param beanReloadStrategy the bean reload strategy
*/
public BeanClassRefreshCommand(ClassLoader appClassLoader, String className, String classSignForProxyCheck,
String classSignByStrategy, BeanReloadStrategy beanReloadStrategy) {
this.appClassLoader = appClassLoader;
this.className = className;
this.classSignForProxyCheck = classSignForProxyCheck;
this.classSignByStrategy = classSignByStrategy;
this.strBeanReloadStrategy = beanReloadStrategy != null ? beanReloadStrategy.toString() : null;
}
/**
* Instantiates a new bean class refresh command.
*
* @param appClassLoader the application class loader
* @param archivePath the archive path
* @param event the class event
*/
public BeanClassRefreshCommand(ClassLoader appClassLoader, String archivePath, WatchFileEvent event) {
this.appClassLoader = appClassLoader;
this.event = event;
// strip from URI prefix up to basePackage and .class suffix.
String classFullPath = event.getURI().getPath();
int index = classFullPath.indexOf(archivePath);
if (index == 0) {
String classPath = classFullPath.substring(archivePath.length());
classPath = classPath.substring(0, classPath.indexOf(".class"));
if (classPath.startsWith("/")) {
classPath = classPath.substring(1);
}
this.className = classPath.replace("/", ".");
} else {
LOGGER.error("Archive path '{}' doesn't match with classFullPath '{}'", archivePath, classFullPath);
}
}
@Override
public void executeCommand() {
List<Command> mergedCommands = popMergedCommands();
mergedCommands.add(0, this);
do {
// First step : recreate all proxies
for (Command command: mergedCommands) {
((BeanClassRefreshCommand)command).recreateProxy(mergedCommands);
}
// Second step : reload beans
for (Command command: mergedCommands) {
((BeanClassRefreshCommand)command).reloadBean(mergedCommands);
}
mergedCommands = popMergedCommands();
} while (!mergedCommands.isEmpty());
}
private void recreateProxy(List<Command> mergedCommands) {
if (isCreateEvent(mergedCommands) || isDeleteEvent(mergedCommands)) {
LOGGER.trace("Skip OWB recreate proxy for delete event on class '{}'", className);
return;
}
if (className != null) {
try {
LOGGER.debug("Executing ProxyRefreshAgent.recreateProxy('{}')", className);
Class<?> agentClazz = Class.forName(ProxyRefreshAgent.class.getName(), true, appClassLoader);
Method agentMethod = agentClazz.getDeclaredMethod("recreateProxy",
new Class[] { ClassLoader.class,
String.class,
String.class
}
);
agentMethod.invoke(null,
appClassLoader,
className,
classSignForProxyCheck
);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Plugin error, method not found", e);
} catch (InvocationTargetException e) {
LOGGER.error("Error recreateProxy class {} in classLoader {}", e, className, appClassLoader);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Plugin error, illegal access", e);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Plugin error, CDI class not found in classloader", e);
}
}
}
public void reloadBean(List<Command> mergedCommands) {
if (isDeleteEvent(mergedCommands)) {
LOGGER.trace("Skip OWB reload for delete event on class '{}'", className);
return;
}
if (className != null) {
try {
LOGGER.debug("Executing BeanClassRefreshAgent.reloadBean('{}')", className);
Class<?> agentClazz = Class.forName(BeanClassRefreshAgent.class.getName(), true, appClassLoader);
Method agentMethod = agentClazz.getDeclaredMethod("reloadBean",
new Class[] { ClassLoader.class,
String.class,
String.class,
String.class
}
);
agentMethod.invoke(null,
appClassLoader,
className,
classSignByStrategy,
strBeanReloadStrategy // passed as String since BeanArchiveAgent has different classloader
);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Plugin error, method not found", e);
} catch (InvocationTargetException e) {
LOGGER.error("Error reloadBean class {} in classLoader {}", e, className, appClassLoader);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Plugin error, illegal access", e);
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Plugin error, CDI class not found in classloader", e);
}
}
}
/**
* Check all merged events with same className for delete and create events. If delete without create is found, than assume
* file was deleted.
* @param mergedCommands
*/
private boolean isDeleteEvent(List<Command> mergedCommands) {
boolean createFound = false;
boolean deleteFound = false;
for (Command cmd : mergedCommands) {
BeanClassRefreshCommand refreshCommand = (BeanClassRefreshCommand) cmd;
if (className.equals(refreshCommand.className)) {
if (refreshCommand.event != null) {
if (refreshCommand.event.getEventType().equals(FileEvent.DELETE))
deleteFound = true;
if (refreshCommand.event.getEventType().equals(FileEvent.CREATE))
createFound = true;
}
}
}
LOGGER.trace("isDeleteEvent result {}: createFound={}, deleteFound={}", createFound, deleteFound);
return !createFound && deleteFound;
}
/**
* Check all merged events with same className for create events.
* @param mergedCommands
*/
private boolean isCreateEvent(List<Command> mergedCommands) {
boolean createFound = false;
for (Command cmd : mergedCommands) {
BeanClassRefreshCommand refreshCommand = (BeanClassRefreshCommand) cmd;
if (className.equals(refreshCommand.className)) {
if (refreshCommand.event != null) {
if (refreshCommand.event.getEventType().equals(FileEvent.CREATE))
createFound = true;
}
}
}
LOGGER.trace("isCreateEvent result {}: createFound={}", createFound);
return createFound;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BeanClassRefreshCommand that = (BeanClassRefreshCommand) o;
if (!appClassLoader.equals(that.appClassLoader)) return false;
return true;
}
@Override
public int hashCode() {
int result = appClassLoader.hashCode();
result = 31 * result + (className != null ? className.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BeanClassRefreshCommand{" +
"appClassLoader=" + appClassLoader +
", className='" + className + '\'' +
'}';
}
}