package org.hotswap.agent.plugin.spring.scanner;
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.util.IOUtils;
import org.hotswap.agent.watch.WatchFileEvent;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Do refresh Spring class (scanned by classpath scanner) based on URI or byte[] definition.
*
* This commands merges events of watcher.event(CREATE) and transformer hotswap reload to a single refresh command.
*/
public class ClassPathBeanRefreshCommand extends MergeableCommand {
private static AgentLogger LOGGER = AgentLogger.getLogger(ClassPathBeanRefreshCommand.class);
ClassLoader appClassLoader;
String basePackage;
String className;
// either event or classDefinition is set by constructor (watcher or transformer)
WatchFileEvent event;
byte[] classDefinition;
public ClassPathBeanRefreshCommand(ClassLoader appClassLoader, String basePackage, String className, byte[] classDefinition) {
this.appClassLoader = appClassLoader;
this.basePackage = basePackage;
this.className = className;
this.classDefinition = classDefinition;
}
public ClassPathBeanRefreshCommand(ClassLoader appClassLoader, String basePackage, WatchFileEvent event) {
this.appClassLoader = appClassLoader;
this.basePackage = basePackage;
this.event = event;
// strip from URI prefix up to basePackage and .class suffix.
String path = event.getURI().getPath();
path = path.substring(path.indexOf(basePackage.replace(".", "/")));
path = path.substring(0, path.indexOf(".class"));
this.className = path;
}
@Override
public void executeCommand() {
if (isDeleteEvent()) {
LOGGER.trace("Skip Spring reload for delete event on class '{}'", className);
return;
}
try {
if (classDefinition == null) {
try {
this.classDefinition = IOUtils.toByteArray(event.getURI());
} catch (IllegalArgumentException e) {
LOGGER.debug("File {} not found on filesystem (deleted?). Unable to refresh associated Spring bean.", event.getURI());
return;
}
}
LOGGER.debug("Executing ClassPathBeanDefinitionScannerAgent.refreshClass('{}')", className);
Class<?> clazz = Class.forName(ClassPathBeanDefinitionScannerAgent.class.getName(), true, appClassLoader);
Method method = clazz.getDeclaredMethod(
"refreshClass", new Class[] {String.class, byte[].class});
method.invoke(null, basePackage, classDefinition);
} catch (NoSuchMethodException e) {
throw new IllegalStateException("Plugin error, method not found", e);
} catch (InvocationTargetException e) {
LOGGER.error("Error refreshing 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, Spring class not found in application classloader", e);
}
}
/**
* Check all merged events for delete and create events. If delete without create is found, than assume
* file was deleted.
*/
private boolean isDeleteEvent() {
// for all merged commands including this command
List<ClassPathBeanRefreshCommand> mergedCommands = new ArrayList<ClassPathBeanRefreshCommand>();
for (Command command : getMergedCommands()) {
mergedCommands.add((ClassPathBeanRefreshCommand) command);
}
mergedCommands.add(this);
boolean createFound = false;
boolean deleteFound = false;
for (ClassPathBeanRefreshCommand command : mergedCommands) {
if (command.event != null) {
if (command.event.getEventType().equals(FileEvent.DELETE))
deleteFound = true;
if (command.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;
ClassPathBeanRefreshCommand that = (ClassPathBeanRefreshCommand) o;
if (!appClassLoader.equals(that.appClassLoader)) return false;
if (!className.equals(that.className)) return false;
return true;
}
@Override
public int hashCode() {
int result = appClassLoader.hashCode();
result = 31 * result + className.hashCode();
return result;
}
@Override
public String toString() {
return "ClassPathBeanRefreshCommand{" +
"appClassLoader=" + appClassLoader +
", basePackage='" + basePackage + '\'' +
", className='" + className + '\'' +
'}';
}
}