package org.hotswap.agent.plugin.webobjects;
import static org.hotswap.agent.annotation.LoadEvent.REDEFINE;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.hotswap.agent.annotation.Init;
import org.hotswap.agent.annotation.OnClassLoadEvent;
import org.hotswap.agent.annotation.Plugin;
import org.hotswap.agent.command.Command;
import org.hotswap.agent.command.Scheduler;
import org.hotswap.agent.config.PluginConfiguration;
import org.hotswap.agent.javassist.CannotCompileException;
import org.hotswap.agent.javassist.ClassPool;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.CtMethod;
import org.hotswap.agent.javassist.NotFoundException;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.util.PluginManagerInvoker;
@Plugin(name = "WebObjects", description = "Hotswap agent plugin for WebObjects app.",
testedVersions = "5.4.3",
expectedVersions = "5.4.3")
public class HotswapWebObjectsPlugin {
// Agent logger is a very simple custom logging mechanism. Do not use any common logging framework
// to avoid compatibility and classloading issues.
private static AgentLogger LOGGER = AgentLogger.getLogger(HotswapWebObjectsPlugin.class);
@Init
Scheduler scheduler;
private Method kvcDefaultImplementation_flushCaches;
private Method kvcReflectionKeyBindingCreation_flushCaches;
private Method kvcValueAccessor_flushCaches;
private Method nsValidationDefaultImplementation_flushCaches;
private Method woApplication_removeComponentDefinitionCacheContents;
private Object woApplicationObject;
private Method nsThreadsafeMutableDictionary_removeAllObjects;
private Object actionClassesCacheDictionnary;
private CtClass woActionCtClass;
private CtClass woComponentCtClass;
private CtClass nsValidationCtClass;
@OnClassLoadEvent(classNameRegexp = "com.webobjects.appserver.WOApplication")
public static void webObjectsIsStarting(CtClass ctClass) throws NotFoundException, CannotCompileException {
CtMethod init = ctClass.getDeclaredMethod("run");
init.insertBefore(PluginManagerInvoker.buildInitializePlugin(HotswapWebObjectsPlugin.class));
LOGGER.debug("WOApplication.run() enhanced with plugin initialization.");
}
// We use reflection to get the methods from WebObjects because the jar is not distribuable publicly
// and we want to build witout it.
@Init
@SuppressWarnings({ "rawtypes", "unchecked" })
public void init(PluginConfiguration pluginConfiguration, ClassLoader appClassLoader) {
try {
Class kvcDefaultImplementationClass = Class.forName("com.webobjects.foundation.NSKeyValueCoding$DefaultImplementation", false, appClassLoader);
kvcDefaultImplementation_flushCaches = kvcDefaultImplementationClass.getMethod("_flushCaches");
Class kvcReflectionKeyBindingCreationClass = Class.forName("com.webobjects.foundation.NSKeyValueCoding$_ReflectionKeyBindingCreation", false, appClassLoader);
kvcReflectionKeyBindingCreation_flushCaches = kvcReflectionKeyBindingCreationClass.getMethod("_flushCaches");
Class kvcValueAccessorClass = Class.forName("com.webobjects.foundation.NSKeyValueCoding$ValueAccessor", false, appClassLoader);
kvcValueAccessor_flushCaches = kvcValueAccessorClass.getMethod("_flushCaches");
Class nsValidationDefaultImplementationClass = Class.forName("com.webobjects.foundation.NSValidation$DefaultImplementation", false, appClassLoader);
nsValidationDefaultImplementation_flushCaches = nsValidationDefaultImplementationClass.getMethod("_flushCaches");
Class woApplicationClass = Class.forName("com.webobjects.appserver.WOApplication", false, appClassLoader);
woApplication_removeComponentDefinitionCacheContents = woApplicationClass.getMethod("_removeComponentDefinitionCacheContents");
woApplicationObject = woApplicationClass.getMethod("application").invoke(null);
ClassPool classPool = ClassPool.getDefault();
woComponentCtClass = classPool.makeClass("com.webobjects.appserver.WOComponent");
nsValidationCtClass = classPool.makeClass("com.webobjects.foundation.NSValidation");
woActionCtClass = classPool.makeClass("com.webobjects.appserver.WOAction");
Class woActionClass = Class.forName("com.webobjects.appserver.WOAction", false, appClassLoader);
Field actionClassesField = woActionClass.getDeclaredField("_actionClasses");
actionClassesField.setAccessible(true);
actionClassesCacheDictionnary = actionClassesField.get(null);
Class nsThreadsafeMutableDictionaryClass = Class.forName("com.webobjects.foundation._NSThreadsafeMutableDictionary", false, appClassLoader);
woApplication_removeComponentDefinitionCacheContents = woApplicationClass.getMethod("_removeComponentDefinitionCacheContents");
nsThreadsafeMutableDictionary_removeAllObjects = nsThreadsafeMutableDictionaryClass.getMethod("removeAllObjects");
} catch (Exception e) {
e.printStackTrace();
}
}
@OnClassLoadEvent(classNameRegexp = ".*", events = REDEFINE)
public void reloadClass(CtClass ctClass) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, CannotCompileException {
LOGGER.debug("Class "+ctClass.getSimpleName()+" redefined.");
scheduler.scheduleCommand(clearKVCCacheCommand);
scheduler.scheduleCommand(clearValidationCacheCommand);
woApplication_removeComponentDefinitionCacheContents.invoke(woApplicationObject);
if (ctClass.subclassOf(woComponentCtClass)) {
scheduler.scheduleCommand(clearComponentCacheCommand);
}
if (ctClass.subclassOf(woActionCtClass)) {
scheduler.scheduleCommand(clearActionCacheCommand);
}
}
private ClearKVCCache clearKVCCacheCommand = new ClearKVCCache();
public class ClearKVCCache implements Command {
@Override
public void executeCommand() {
try {
kvcDefaultImplementation_flushCaches.invoke(null);
kvcReflectionKeyBindingCreation_flushCaches.invoke(null);
kvcValueAccessor_flushCaches.invoke(null);
LOGGER.info("Resetting KeyValueCoding caches");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private ClearComponentCache clearComponentCacheCommand = new ClearComponentCache();
public class ClearComponentCache implements Command {
@Override
public void executeCommand() {
try {
woApplication_removeComponentDefinitionCacheContents.invoke(woApplicationObject);
LOGGER.info("Resetting Component Definition cache");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private ClearActionCache clearActionCacheCommand = new ClearActionCache();
public class ClearActionCache implements Command {
@Override
public void executeCommand() {
try {
nsThreadsafeMutableDictionary_removeAllObjects.invoke(actionClassesCacheDictionnary);
LOGGER.info("Resetting Action class cache");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private ClearValidationCache clearValidationCacheCommand = new ClearValidationCache();
public class ClearValidationCache implements Command {
@Override
public void executeCommand() {
try {
nsValidationDefaultImplementation_flushCaches.invoke(null);
LOGGER.info("Resetting NSValidation cache");
} catch (Exception e) {
e.printStackTrace();
}
}
}
}