package com.avaje.ebean.enhance.agent; 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 for enhancing entities. * <p> * Used for javaagent or ant etc to modify the class with field interception. * </p> * <p> * This is NOT used for subclass generation. * </p> */ public class ClassAdpaterEntity extends ClassVisitor implements EnhanceConstants { private final EnhanceContext enhanceContext; private final ClassLoader classLoader; private final ClassMeta classMeta; private boolean firstMethod = true; public ClassAdpaterEntity(ClassVisitor cv, ClassLoader classLoader, EnhanceContext context) { super(Opcodes.ASM5, cv); this.classLoader = classLoader; this.enhanceContext = context; this.classMeta = context.createClassMeta(); } /** * Log that the class has been enhanced. */ public void logEnhanced() { classMeta.logEnhanced(); } public boolean isLog(int level){ return classMeta.isLog(level); } public void log(String msg){ classMeta.log(msg); } /** * Create the class definition replacing the className and super class. */ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { classMeta.setClassName(name, superName); int n = 1 + interfaces.length; String[] c = new String[n]; for (int i = 0; i < interfaces.length; i++) { c[i] = interfaces[i]; if (c[i].equals(C_ENTITYBEAN)) { classMeta.setEntityBeanInterface(true); } if (c[i].equals(C_SCALAOBJECT)) { classMeta.setScalaInterface(true); } if (c[i].equals(C_GROOVYOBJECT)) { classMeta.setGroovyInterface(true); } } if (classMeta.hasEntityBeanInterface()){ // Just use the original interfaces c = interfaces; } else { // Add the EntityBean interface c[c.length - 1] = C_ENTITYBEAN; } if (!superName.equals("java/lang/Object")){ // read information about superClasses... if (classMeta.isLog(7)){ classMeta.log("read information about superClasses "+superName +" to see if it is entity/embedded/mappedSuperclass"); } ClassMeta superMeta = enhanceContext.getSuperMeta(superName, classLoader); if (superMeta != null && superMeta.isEntity()){ // the superClass is an entity/embedded/mappedSuperclass... classMeta.setSuperMeta(superMeta); if (classMeta.isLog(1)){ classMeta.log("entity extends "+superMeta.getDescription()); } } else { if (classMeta.isLog(7)){ if (superMeta == null){ classMeta.log("unable to read superMeta for "+superName); } else { classMeta.log("superMeta "+superName+" is not an entity/embedded/mappedsuperclass "+superMeta.getClassAnnotations()); } } } } super.visit(version, access, name, signature, superName, c); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { classMeta.addClassAnnotation(desc); return super.visitAnnotation(desc, visible); } /** * 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; classMeta.log(m); } return true; } return false; } private boolean isPropertyChangeListenerField(String name, String desc, String signature) { if (desc.equals("Ljava/beans/PropertyChangeSupport;")){ return true; } return false; } /** * The ebeanIntercept field is added once but thats all. Note the other * fields are defined in the superclass. */ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { if ((access & Opcodes.ACC_STATIC) != 0) { // static field... if (isEbeanFieldMarker(name, desc, signature)){ classMeta.setAlreadyEnhanced(true); if (isLog(2)){ log("Found ebean marker field "+name+" "+value); } } else { if (isLog(2)){ log("Skip intercepting static field "+name); } } // no interception of static fields return super.visitField(access, name, desc, signature, value); } if (isPropertyChangeListenerField(name, desc, signature)) { //classMeta.setExistingPropertyChangeSupport(name); if (isLog(1)){ classMeta.log("Found existing PropertyChangeSupport field "+name); } // no interception on PropertyChangeSupport field return super.visitField(access, name, desc, signature, value); } if ((access & Opcodes.ACC_TRANSIENT) != 0) { if (isLog(2)){ log("Skip intercepting transient field "+name); } // no interception of transient fields return super.visitField(access, name, desc, signature, value); } // this will hide the field on the super object... // but being in a different ClassLoader means we don't // get access to those 'real' private fields FieldVisitor fv = super.visitField(access, name, desc, signature, value); return classMeta.createLocalFieldVisitor(cv, fv, name, desc); } /** * Replace the method code with field interception. */ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (firstMethod){ if (!classMeta.isEntityEnhancementRequired()) { // skip the rest of the visiting etc throw new NoEnhancementRequiredException(); } if (classMeta.hasEntityBeanInterface()){ log("Enhancing when EntityBean interface already exists!"); } // always add the marker field on every enhanced class String marker = MarkerField.addField(cv, classMeta.getClassName()); if (isLog(4)){ log("... add marker field \""+marker+"\""); } if (!classMeta.isSuperClassEntity()){ // only add the intercept and identity fields if // the superClass is not also enhanced if (isLog(4)){ log("... add intercept and identity fields"); } InterceptField.addField(cv, enhanceContext.isTransientInternalFields()); MethodEquals.addIdentityField(cv); } firstMethod = false; } classMeta.addExistingMethod(name, desc); if (isDefaultConstructor(name, desc)){ // make sure the access is public MethodVisitor mv = super.visitMethod(Opcodes.ACC_PUBLIC, name, desc, signature, exceptions); // also create the entityBeanIntercept object return new ConstructorAdapter(mv, classMeta, desc); } MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); if (interceptEntityMethod(access, name, desc, signature, exceptions)) { // change the method replacing the relevant GETFIELD PUTFIELD with // our special field methods with interception... return new MethodFieldAdapter(mv, classMeta, name+" "+desc); } // just leave as is, no interception etc return mv; } /** * Add methods to get and set the entityBeanIntercept. Also add the * writeReplace method to control serialisation. */ public void visitEnd() { if (!classMeta.isEntityEnhancementRequired()){ throw new NoEnhancementRequiredException(); } if (!classMeta.hasDefaultConstructor()){ DefaultConstructor.add(cv, classMeta); } MarkerField.addGetMarker(cv, classMeta.getClassName()); if (!classMeta.isSuperClassEntity()){ // Add the _ebean_getIntercept() _ebean_setIntercept() methods InterceptField.addGetterSetter(cv, classMeta.getClassName()); // Add add/removePropertyChangeListener methods MethodPropertyChangeListener.addMethod(cv, classMeta); } // Add the field set/get methods which are used in place // of GETFIELD PUTFIELD instructions classMeta.addFieldGetSetMethods(cv); //Add the getField(index) and setField(index) methods IndexFieldWeaver.addMethods(cv, classMeta); MethodSetEmbeddedLoaded.addMethod(cv, classMeta); MethodIsEmbeddedNewOrDirty.addMethod(cv, classMeta); MethodNewInstance.addMethod(cv, classMeta); // register with the agentContext enhanceContext.addClassMeta(classMeta); super.visitEnd(); } private boolean isDefaultConstructor(String name, String desc){ if (name.equals("<init>")) { if (desc.equals("()V")) { classMeta.setHasDefaultConstructor(true); } return true; } return false; } private boolean interceptEntityMethod(int access, String name, String desc, String signature, String[] exceptions) { if ((access & Opcodes.ACC_STATIC) != 0) { // no interception of static methods? if (isLog(2)){ log("Skip intercepting static method "+name); } return false; } if (name.equals("hashCode") && desc.equals("()I")) { classMeta.setHasEqualsOrHashcode(true); return true; } if (name.equals("equals") && desc.equals("(Ljava/lang/Object;)Z")) { classMeta.setHasEqualsOrHashcode(true); return true; } if (name.equals("toString") && desc.equals("()Ljava/lang/String;")) { // don't intercept toString as its is used // during debugging etc return false; } return true; } }