package org.hotswap.agent.plugin.weld.command;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
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.weld.BeanReloadStrategy;
import org.hotswap.agent.watch.WatchFileEvent;
/**
* BeanClassRefreshCommand. Collect all classes definitions/redefinitions for single archive
*
* 1. Merge all commands (definition, redefinition) for single archive to single command.
* 2. Call proxy redefinitions in BeanDeploymentArchiveAgent for all merged commands
* 3. Call bean class reload in BeanDepoymentArchiveAgent for all merged commands
*
* @author Vladimir Dvorak
*/
public class BeanClassRefreshCommand extends MergeableCommand {
private static AgentLogger LOGGER = AgentLogger.getLogger(BeanClassRefreshCommand.class);
ClassLoader classLoader;
String archivePath;
String className;
String classSignatureForProxyCheck;
String classSignatureByStrategy;
String strBeanReloadStrategy;
Map<Object, Object> registeredProxiedBeans;
// either event or classDefinition is set by constructor (watcher or transformer)
WatchFileEvent event;
public BeanClassRefreshCommand(ClassLoader classLoader, String archivePath, Map<Object, Object> registeredProxiedBeans,
String className, String classSignaturForProxyCheck, String classSignatureByStrategy, BeanReloadStrategy beanReloadStrategy) {
this.classLoader = classLoader;
this.archivePath = archivePath;
this.registeredProxiedBeans = registeredProxiedBeans;
this.className = className;
this.classSignatureForProxyCheck = classSignaturForProxyCheck;
this.classSignatureByStrategy = classSignatureByStrategy;
this.strBeanReloadStrategy = beanReloadStrategy != null ? beanReloadStrategy.toString() : null;
}
public BeanClassRefreshCommand(ClassLoader classLoader, String normalizedArchivePath, WatchFileEvent event) {
this.classLoader = classLoader;
this.archivePath = normalizedArchivePath;
this.event = event;
// strip from URI prefix up to basePackage and .class suffix.
String classFullPath = event.getURI().getPath();
int index = classFullPath.indexOf(normalizedArchivePath);
if (index == 0) {
String classPath = classFullPath.substring(normalizedArchivePath.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 '{}'", normalizedArchivePath, 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 (isDeleteEvent(mergedCommands)) {
LOGGER.trace("Skip recreate proxy for delete event on class '{}'", className);
return;
}
if (className != null) {
try {
LOGGER.debug("Executing BeanDeploymentArchiveAgent.recreateProxy('{}')", className);
Class<?> bdaAgentClazz = Class.forName(BeanDeploymentArchiveAgent.class.getName(), true, classLoader);
Method recreateProxy = bdaAgentClazz.getDeclaredMethod("recreateProxy",
new Class[] { ClassLoader.class,
String.class,
Map.class,
String.class,
String.class
}
);
recreateProxy.invoke(null,
classLoader,
archivePath,
registeredProxiedBeans,
className,
classSignatureForProxyCheck
);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Plugin error, method not found", e);
} catch (InvocationTargetException e) {
LOGGER.error("Error recreate proxy class {} in classLoader {}", e, className, classLoader);
} 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);
}
}
}
private void reloadBean(List<Command> mergedCommands) {
if (isDeleteEvent(mergedCommands)) {
LOGGER.trace("Skip refresh bean class for delete event on class '{}'", className);
return;
}
if (className != null) {
try {
LOGGER.debug("Executing BeanDeploymentArchiveAgent.reloadBean('{}')", className);
Class<?> bdaAgentClazz = Class.forName(BeanDeploymentArchiveAgent.class.getName(), true, classLoader);
Method refreshBean = bdaAgentClazz.getDeclaredMethod("reloadBean",
new Class[] { ClassLoader.class,
String.class,
String.class,
String.class,
String.class
}
);
refreshBean.invoke(null,
classLoader,
archivePath,
className,
classSignatureByStrategy,
strBeanReloadStrategy // passed as String since BeanDeploymentArchiveAgent 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, classLoader);
} 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;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BeanClassRefreshCommand that = (BeanClassRefreshCommand) o;
if (!classLoader.equals(that.classLoader)) return false;
if (archivePath != null && !archivePath.equals(that.archivePath)) return false;
return true;
}
@Override
public int hashCode() {
int result = classLoader.hashCode();
result = 31 * result + (className != null ? className.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "BeanClassRefreshCommand{" +
"classLoader=" + classLoader +
", archivePath='" + archivePath + '\'' +
", className='" + className + '\'' +
'}';
}
}