package com.avaje.ebean.enhance.agent;
import java.io.PrintStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.net.URL;
import java.security.ProtectionDomain;
import com.avaje.ebean.enhance.asm.ClassReader;
import com.avaje.ebean.enhance.asm.ClassWriter;
/**
* A Class file Transformer that enhances entity beans.
* <p>
* This is used as both a javaagent or via an ANT task (or other off line
* approach).
* </p>
*/
public class Transformer implements ClassFileTransformer {
public static void premain(String agentArgs, Instrumentation inst) {
Transformer t = new Transformer("", agentArgs);
inst.addTransformer(t);
if (t.getLogLevel() > 0) {
System.out.println("premain loading Transformer with args:" + agentArgs);
}
}
public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
Transformer t = new Transformer("", agentArgs);
inst.addTransformer(t);
if (t.getLogLevel() > 0) {
System.out.println("agentmain loading Transformer with args:" + agentArgs);
}
}
private static final int CLASS_WRITER_COMPUTEFLAGS = ClassWriter.COMPUTE_FRAMES
+ ClassWriter.COMPUTE_MAXS;
private final EnhanceContext enhanceContext;
private boolean performDetect;
private boolean transformTransactional;
private boolean transformEntityBeans;
public Transformer(String extraClassPath, String agentArgs) {
this(parseClassPaths(extraClassPath), agentArgs);
}
public Transformer(URL[] extraClassPath, String agentArgs) {
this(new ClassPathClassBytesReader(extraClassPath), agentArgs);
}
public Transformer(ClassBytesReader r, String agentArgs) {
this.enhanceContext = new EnhanceContext(r, agentArgs);
this.performDetect = enhanceContext.getPropertyBoolean("detect", true);
this.transformTransactional = enhanceContext.getPropertyBoolean("transactional", true);
this.transformEntityBeans = enhanceContext.getPropertyBoolean("entity", true);
}
/**
* Override when you need transformation to occur with knowledge of other
* classes.
* <p>
* Note: Added to support Play framework.
* </p>
*/
protected ClassWriter createClassWriter() {
return new ClassWriter(CLASS_WRITER_COMPUTEFLAGS);
}
/**
* Change the logout to something other than system out.
*/
public void setLogout(PrintStream logout) {
this.enhanceContext.setLogout(logout);
}
public void log(int level, String msg) {
enhanceContext.log(level, msg);
}
public int getLogLevel() {
return enhanceContext.getLogLevel();
}
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
// ignore JDK and JDBC classes etc
if (enhanceContext.isIgnoreClass(className)) {
enhanceContext.log(9, "ignore class " + className);
return null;
}
ClassAdapterDetectEnhancement detect = null;
if (performDetect) {
enhanceContext.log(5, "performing detection on " + className);
detect = detect(loader, classfileBuffer);
}
if (detect == null) {
// default only looks entity beans to enhance
enhanceContext.log(1, "no detection so enhancing entity " + className);
return entityEnhancement(loader, classfileBuffer);
}
if (transformEntityBeans && detect.isEntity()) {
if (detect.isEnhancedEntity()) {
detect.log(1, "already enhanced entity");
} else {
//
detect.log(2, "performing entity transform");
return entityEnhancement(loader, classfileBuffer);
}
}
if (transformTransactional && detect.isTransactional()) {
if (detect.isEnhancedTransactional()) {
detect.log(1, "already enhanced transactional");
} else {
detect.log(2, "performing transactional transform");
return transactionalEnhancement(loader, classfileBuffer);
}
}
enhanceContext.log(9, "no enhancement on class " + className);
return null;
} catch (NoEnhancementRequiredException e) {
// the class is an interface
log(8, "No Enhancement required " + e.getMessage());
return null;
} catch (Exception e) {
// a safety net for unexpected errors
// in the transformation
enhanceContext.log(e);
return null;
}
}
/**
* Perform entity bean enhancement.
*/
private byte[] entityEnhancement(ClassLoader loader, byte[] classfileBuffer) {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = createClassWriter();
ClassAdpaterEntity ca = new ClassAdpaterEntity(cw, loader, enhanceContext);
try {
cr.accept(ca, 0);
if (ca.isLog(1)) {
ca.logEnhanced();
}
if (enhanceContext.isReadOnly()) {
return null;
} else {
return cw.toByteArray();
}
} catch (AlreadyEnhancedException e) {
if (ca.isLog(1)) {
ca.log("already enhanced entity");
}
return null;
} catch (NoEnhancementRequiredException e) {
if (ca.isLog(2)) {
ca.log("skipping... no enhancement required");
}
return null;
}
}
/**
* Perform transactional enhancement.
*/
private byte[] transactionalEnhancement(ClassLoader loader, byte[] classfileBuffer) {
ClassReader cr = new ClassReader(classfileBuffer);
ClassWriter cw = createClassWriter();
ClassAdapterTransactional ca = new ClassAdapterTransactional(cw, loader, enhanceContext);
try {
cr.accept(ca, ClassReader.EXPAND_FRAMES);
if (ca.isLog(1)) {
ca.log("enhanced");
}
if (enhanceContext.isReadOnly()) {
return null;
} else {
return cw.toByteArray();
}
} catch (AlreadyEnhancedException e) {
if (ca.isLog(1)) {
ca.log("already enhanced");
}
return null;
} catch (NoEnhancementRequiredException e) {
if (ca.isLog(0)) {
ca.log("skipping... no enhancement required");
}
return null;
}
}
/**
* Helper method to split semi-colon separated class paths into a URL array.
*/
public static URL[] parseClassPaths(String extraClassPath) {
if (extraClassPath == null) {
return new URL[0];
}
String[] stringPaths = extraClassPath.split(";");
return UrlPathHelper.convertToUrl(stringPaths);
}
/**
* Read the bytes quickly trying to detect if it needs entity or transactional
* enhancement.
*/
private ClassAdapterDetectEnhancement detect(ClassLoader classLoader, byte[] classfileBuffer) {
ClassAdapterDetectEnhancement detect = new ClassAdapterDetectEnhancement(classLoader,
enhanceContext);
// skip what we can...
ClassReader cr = new ClassReader(classfileBuffer);
cr.accept(detect, ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES);
return detect;
}
}