package com.avaje.ebean.enhance.agent;
import java.util.ArrayList;
import java.util.HashSet;
import com.avaje.ebean.enhance.asm.AnnotationVisitor;
import com.avaje.ebean.enhance.asm.ClassVisitor;
import com.avaje.ebean.enhance.asm.FieldVisitor;
import com.avaje.ebean.enhance.asm.MethodVisitor;
import com.avaje.ebean.enhance.asm.Opcodes;
/**
* ClassAdapter used to detect if this class needs enhancement for entity or
* transactional support.
*/
public class ClassAdapterDetectEnhancement extends ClassVisitor {
private final ClassLoader classLoader;
private final EnhanceContext enhanceContext;
private final HashSet<String> classAnnotation = new HashSet<String>();
private final ArrayList<DetectMethod> methods = new ArrayList<DetectMethod>();
private String className;
private boolean entity;
private boolean entityInterface;
private boolean entityField;
private boolean transactional;
private boolean enhancedTransactional;
public ClassAdapterDetectEnhancement(ClassLoader classLoader, EnhanceContext context) {
super(Opcodes.ASM5);
this.classLoader = classLoader;
this.enhanceContext = context;
}
public boolean isEntityOrTransactional() {
return entity || isTransactional();
}
public String getStatus() {
String s = "class: " + className;
if (isEntity()) {
s += " entity:true enhanced:" + entityField;
s = "*" + s;
} else if (isTransactional()) {
s += " transactional:true enhanced:" + enhancedTransactional;
s = "*" + s;
} else {
s = " " + s;
}
return s;
}
public boolean isLog(int level) {
return enhanceContext.isLog(level);
}
public void log(String msg) {
enhanceContext.log(className, msg);
}
public void log(int level, String msg) {
if (isLog(level)){
log(msg);
}
}
public boolean isEnhancedEntity() {
return entityField;
}
public boolean isEnhancedTransactional() {
return enhancedTransactional;
}
/**
* Return true if this is an entity bean or embeddable bean.
*/
public boolean isEntity() {
return entity;
}
/**
* Return true if ANY method has the transactional annotation.
*/
public boolean isTransactional() {
if (transactional){
// implements transactional interface or
// transactional at class level
return transactional;
}
// check each method...
for (int i = 0; i < methods.size(); i++) {
DetectMethod m = methods.get(i);
if (m.isTransactional()) {
return true;
}
}
return false;
}
/**
* Visit the class with interfaces.
*/
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if ((access & Opcodes.ACC_INTERFACE) != 0){
throw new NoEnhancementRequiredException(name+" is an Interface");
}
className = name;
for (int i = 0; i < interfaces.length; i++) {
if (interfaces[i].equals(EnhanceConstants.C_ENTITYBEAN)) {
entityInterface = true;
entity = true;
} else if (interfaces[i].equals(EnhanceConstants.C_ENHANCEDTRANSACTIONAL)) {
enhancedTransactional = true;
} else {
ClassMeta intefaceMeta = enhanceContext.getInterfaceMeta(interfaces[i], classLoader);
if (intefaceMeta != null && intefaceMeta.isTransactional()) {
// implements transactional interface
transactional = true;
if (isLog(9)) {
log("detected implements tranactional interface " + intefaceMeta);
}
}
}
}
if (isLog(2)){
log("interfaces: entityInterface["+entityInterface+"] transactional["+enhancedTransactional+"]");
}
super.visit(version, access, name, signature, superName, interfaces);
}
/**
* Visit class level annotations.
*/
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (isLog(8)){
log("visitAnnotation "+desc);
}
classAnnotation.add(desc);
if (isEntityAnnotation(desc)){
// entity, embeddable or mappedSuperclass
if (isLog(5)){
log("found entity annotation "+desc);
}
entity = true;
} else if (desc.equals(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION)) {
// class level Transactional annotation
if (isLog(5)){
log("found transactional annotation "+desc);
}
transactional = true;
}
return super.visitAnnotation(desc, visible);
}
/**
* Return true if the annotation is for an Entity, Embeddable or MappedSuperclass.
*/
private boolean isEntityAnnotation(String desc) {
if (desc.equals(EnhanceConstants.ENTITY_ANNOTATION)) {
return true;
} else if (desc.equals(EnhanceConstants.EMBEDDABLE_ANNOTATION)) {
return true;
} else if (desc.equals(EnhanceConstants.MAPPEDSUPERCLASS_ANNOTATION)) {
return true;
}
return false;
}
/**
* Return true if this is the enhancement marker field.
* <p>
* The existence of this field is used to confirm that the class has been
* enhanced (rather than solely relying on the EntityBean interface).
* <p>
*/
private boolean isEbeanFieldMarker(String name, String desc, String signature) {
if (name.equals(MarkerField._EBEAN_MARKER)){
if (!desc.equals("Ljava/lang/String;")){
String m = "Error: _EBEAN_MARKER field of wrong type? "+desc;
log(m);
}
return true;
}
return false;
}
public FieldVisitor visitField(int access, String name, String desc, String signature,
Object value) {
if (isLog(8)){
log("visitField "+name+" "+value);
}
if ((access & Opcodes.ACC_STATIC) != 0) {
// static field...
if (isEbeanFieldMarker(name, desc, signature)){
entityField = true;
if (isLog(1)){
log("Found ebean marker field "+name+" "+value);
}
}
}
return super.visitField(access, name, desc, signature, value);
}
/**
* Visit the methods specifically looking for method level transactional
* annotations.
*/
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
if (isLog(9)){
log("visitMethod "+name+" "+desc);
}
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
DetectMethod dmv = new DetectMethod(mv);
methods.add(dmv);
return dmv;
}
/**
* Check methods for Transactional annotation.
*/
private static class DetectMethod extends MethodVisitor {
boolean transactional;
public DetectMethod(final MethodVisitor mv) {
super(Opcodes.ASM5, mv);
}
/**
* Return true if this method has the transaction annotation supplied.
*/
public boolean isTransactional() {
return transactional;
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (desc.equals(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION)) {
transactional = true;
}
return super.visitAnnotation(desc, visible);
}
}
}