package org.hotswap.agent.plugin.proxy;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.hotswap.agent.annotation.LoadEvent;
import org.hotswap.agent.annotation.OnClassLoadEvent;
import org.hotswap.agent.annotation.Plugin;
import org.hotswap.agent.config.PluginManager;
import org.hotswap.agent.javassist.ClassPool;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.logging.AgentLogger;
import org.hotswap.agent.plugin.proxy.hscglib.CglibEnhancerProxyTransformer;
import org.hotswap.agent.plugin.proxy.hscglib.CglibProxyTransformer;
import org.hotswap.agent.plugin.proxy.hscglib.GeneratorParametersTransformer;
import org.hotswap.agent.plugin.proxy.hscglib.GeneratorParams;
import org.hotswap.agent.util.classloader.ClassLoaderHelper;
/**
* Redefines proxy classes that implement or extend changed interfaces or classes. Currently it supports proxies created
* with Java reflection and the Cglib library.
*
* @author Erki Ehtla, Vladimir Dvorak
*
*/
@Plugin(name = "Proxy", description = "Redefines proxies", testedVersions = { "" }, expectedVersions = { "all" }, supportClass = RedefinitionScheduler.class)
public class ProxyPlugin {
private static AgentLogger LOGGER = AgentLogger.getLogger(ProxyPlugin.class);
static boolean isJava8OrNewer = getVersion() >= 18;
/**
* Flag to check reload status. In unit test we need to wait for reload
* finish before the test can continue. Set flag to true in the test class
* and wait until the flag is false again.
*/
public static boolean reloadFlag = false;
private static Set<String> proxyRedefiningMap = new HashSet<>();
@OnClassLoadEvent(classNameRegexp = "com.sun.proxy.\\$Proxy.*", events = LoadEvent.REDEFINE, skipSynthetic = false)
public static void transformJavaProxy(final Class<?> classBeingRedefined, final ClassLoader classLoader) {
/*
* Proxy can't be redefined directly in this method (and return new proxy class bytes), since the classLoader contains
* OLD definition of proxie's interface. Therefore proxy is defined in deferred command (after some delay)
* after proxied interface is redefined in DCEVM.
*/
if (!ClassLoaderHelper.isClassLoderStarted(classLoader)) {
return;
}
final String className = classBeingRedefined.getName();
if (proxyRedefiningMap.contains(className)) {
proxyRedefiningMap.remove(className);
return;
}
proxyRedefiningMap.add(className);
final Map<String, String> signatureMapOrig = ProxyClassSignatureHelper.getNonSyntheticSignatureMap(classBeingRedefined);
reloadFlag = true;
// TODO: can be single command if scheduler guarantees the keeping execution order in the order of redefinition
PluginManager.getInstance().getScheduler().scheduleCommand(new ReloadJavaProxyCommand(classLoader, className, signatureMapOrig), 50);
}
// @OnClassLoadEvent(classNameRegexp = "com/sun/proxy/\\$Proxy.*", events = LoadEvent.REDEFINE, skipSynthetic = false)
// public static byte[] transformJavaProxy(final Class<?> classBeingRedefined, final byte[] classfileBuffer,
// final ClassPool cp, final CtClass cc) throws IllegalClassFormatException, IOException, RuntimeException {
// try {
// return JavassistProxyTransformer.transform(classBeingRedefined, classfileBuffer, cc, cp);
// } catch (Exception e) {
// LOGGER.error("Error redifining Cglib proxy {}", e, classBeingRedefined.getName());
// }
// return classfileBuffer;
// }
// alternative method of redefining Java proxies, uses a new classlaoder instance
// @OnClassLoadEvent(classNameRegexp = "com/sun/proxy/\\$Proxy.*", events = LoadEvent.REDEFINE, skipSynthetic =
// false)
// public static byte[] transformJavaProxy(final Class<?> classBeingRedefined, final byte[] classfileBuffer,
// final ClassLoader loader) throws IllegalClassFormatException, IOException, RuntimeException {
// try {
// return NewClassLoaderJavaProxyTransformer.transform(classBeingRedefined, classfileBuffer, loader);
// } catch (Exception e) {
// LOGGER.error("Error redifining Cglib proxy {}", e, classBeingRedefined.getName());
// }
// return classfileBuffer;
// }
//
// // alternative method of redefining Java proxies, uses a 2 step process. Crashed with jvm8
// @OnClassLoadEvent(classNameRegexp = "com/sun/proxy/\\$Proxy.*", events = LoadEvent.REDEFINE, skipSynthetic =
// false)
// public static byte[] transformJavaProxy(final Class<?> classBeingRedefined, final byte[] classfileBuffer,
// final ClassPool cp) {
// try {
// return JavaProxyTransformer.transform(classBeingRedefined, cp, classfileBuffer);
// } catch (Exception e) {
// LOGGER.error("Error redifining Cglib proxy {}", e, classBeingRedefined.getName());
// }
// return classfileBuffer;
// }
@OnClassLoadEvent(classNameRegexp = ".*", events = LoadEvent.REDEFINE, skipSynthetic = false)
public static byte[] transformCglibProxy(final Class<?> classBeingRedefined, final byte[] classfileBuffer,
final ClassLoader loader, final ClassPool cp) throws Exception {
GeneratorParams generatorParams = GeneratorParametersTransformer.getGeneratorParams(loader, classBeingRedefined.getName());
if (!ClassLoaderHelper.isClassLoderStarted(loader)) {
return classfileBuffer;
}
if (generatorParams == null) {
return classfileBuffer;
}
// flush standard java beans caches
loader.loadClass("java.beans.Introspector").getMethod("flushCaches").invoke(null);
if (generatorParams.getParam().getClass().getName().endsWith(".Enhancer")) {
try {
return CglibEnhancerProxyTransformer.transform(classBeingRedefined, cp, classfileBuffer, loader, generatorParams);
} catch (Exception e) {
LOGGER.error("Error redifining Cglib Enhancer proxy {}", e, classBeingRedefined.getName());
}
}
// Multistep transformation crashed jvm in java8 u05
if (!isJava8OrNewer)
try {
return CglibProxyTransformer.transform(classBeingRedefined, cp, classfileBuffer, generatorParams);
} catch (Exception e) {
LOGGER.error("Error redifining Cglib proxy {}", e, classBeingRedefined.getName());
}
return classfileBuffer;
}
/**
* Modifies Cglib bytecode generators to store the parameters for this plugin
*
* @throws Exception
*/
@OnClassLoadEvent(classNameRegexp = ".*/cglib/.*", skipSynthetic = false)
public static CtClass transformDefinitions(CtClass cc) throws Exception {
try {
return GeneratorParametersTransformer.transform(cc);
} catch (Exception e) {
LOGGER.error("Error modifying class for cglib proxy creation parameter recording", e);
}
return cc;
}
private static int getVersion() {
String version = System.getProperty("java.version");
int pos = 0, count = 0;
for (; pos < version.length() && count < 2; pos++) {
if (version.charAt(pos) == '.')
count++;
}
return Integer.valueOf(version.substring(0, pos).replace(".", ""));
}
}