001package io.ebean.enhance.entity; 002 003import io.ebean.enhance.asm.AnnotationVisitor; 004import io.ebean.enhance.asm.ClassVisitor; 005import io.ebean.enhance.asm.FieldVisitor; 006import io.ebean.enhance.asm.MethodVisitor; 007import io.ebean.enhance.asm.Opcodes; 008import io.ebean.enhance.common.ClassMeta; 009import io.ebean.enhance.common.EnhanceConstants; 010import io.ebean.enhance.common.EnhanceContext; 011import io.ebean.enhance.common.NoEnhancementRequiredException; 012 013import static io.ebean.enhance.Transformer.EBEAN_ASM_VERSION; 014 015/** 016 * ClassAdapter for enhancing entities. 017 * <p> 018 * Used for javaagent or ant etc to modify the class with field interception. 019 * </p> 020 * <p> 021 * This is NOT used for subclass generation. 022 * </p> 023 */ 024public class ClassAdapterEntity extends ClassVisitor implements EnhanceConstants { 025 026 private final EnhanceContext enhanceContext; 027 028 private final ClassLoader classLoader; 029 030 private final ClassMeta classMeta; 031 032 private boolean firstMethod = true; 033 034 public ClassAdapterEntity(ClassVisitor cv, ClassLoader classLoader, EnhanceContext context) { 035 super(EBEAN_ASM_VERSION, cv); 036 this.classLoader = classLoader; 037 this.enhanceContext = context; 038 this.classMeta = context.createClassMeta(); 039 } 040 041 /** 042 * Log that the class has been enhanced. 043 */ 044 public void logEnhanced() { 045 classMeta.logEnhanced(); 046 } 047 048 public boolean isLog(int level) { 049 return classMeta.isLog(level); 050 } 051 052 public void log(String msg) { 053 classMeta.log(msg); 054 } 055 056 /** 057 * Create the class definition replacing the className and super class. 058 */ 059 @Override 060 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 061 skipMockitoMock(name); 062 classMeta.setClassName(name, superName); 063 064 String[] c = new String[interfaces.length + 1]; 065 for (int i = 0; i < interfaces.length; i++) { 066 c[i] = interfaces[i]; 067 if (c[i].equals(C_ENTITYBEAN)) { 068 throw new NoEnhancementRequiredException(); 069 } 070 if (c[i].equals(C_SCALAOBJECT)) { 071 classMeta.setScalaInterface(true); 072 } 073 if (c[i].equals(C_GROOVYOBJECT)) { 074 classMeta.setGroovyInterface(true); 075 } 076 } 077 // add the EntityBean interface 078 c[c.length - 1] = C_ENTITYBEAN; 079 if (classMeta.isLog(8)) { 080 classMeta.log("... add EntityBean interface"); 081 } 082 if (!superName.equals("java/lang/Object")) { 083 // read information about superClasses... 084 if (classMeta.isLog(7)) { 085 classMeta.log("read information about superClasses " + superName + " to see if it is entity/embedded/mappedSuperclass"); 086 } 087 ClassMeta superMeta = enhanceContext.getSuperMeta(superName, classLoader); 088 if (superMeta != null && superMeta.isEntity()) { 089 // the superClass is an entity/embedded/mappedSuperclass... 090 classMeta.setSuperMeta(superMeta); 091 } 092 } 093 super.visit(version, access, name, signature, superName, c); 094 } 095 096 /** 097 * Do not enhance a Mockito mock or spy. 098 */ 099 private void skipMockitoMock(String name) { 100 if (name.contains(MOCKITO_MOCK)) { 101 throw new NoEnhancementRequiredException(); 102 } 103 } 104 105 @Override 106 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 107 classMeta.addClassAnnotation(desc); 108 return super.visitAnnotation(desc, visible); 109 } 110 111 private boolean isPropertyChangeListenerField(String desc) { 112 return desc.equals("Ljava/beans/PropertyChangeSupport;"); 113 } 114 115 /** 116 * The ebeanIntercept field is added once but thats all. Note the other 117 * fields are defined in the superclass. 118 */ 119 @Override 120 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 121 if ((access & Opcodes.ACC_STATIC) != 0) { 122 // no interception of static fields 123 return super.visitField(access, name, desc, signature, value); 124 } 125 if (isPropertyChangeListenerField(desc)) { 126 if (isLog(4)) { 127 classMeta.log("Found existing PropertyChangeSupport field " + name); 128 } 129 // no interception on PropertyChangeSupport field 130 return super.visitField(access, name, desc, signature, value); 131 } 132 if ((access & Opcodes.ACC_TRANSIENT) != 0) { 133 // no interception of transient fields 134 return super.visitField(access, name, desc, signature, value); 135 } 136 if ((access & Opcodes.ACC_FINAL) != 0) { 137 // remove final modifier from fields (for lazy loading partials in Java9+) 138 access = (access ^ Opcodes.ACC_FINAL); 139 } 140 FieldVisitor fv = super.visitField(access, name, desc, signature, value); 141 return classMeta.createLocalFieldVisitor(fv, name, desc); 142 } 143 144 /** 145 * Replace the method code with field interception. 146 */ 147 @Override 148 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 149 if (firstMethod) { 150 if (classMeta.isAlreadyEnhanced()) { 151 throw new NoEnhancementRequiredException(); 152 } 153 if (classMeta.hasEntityBeanInterface()) { 154 log("Enhancing when EntityBean interface already exists!"); 155 } 156 IndexFieldWeaver.addPropertiesField(cv, classMeta); 157 if (isLog(4)) { 158 log("... add _ebean_props field"); 159 } 160 if (!classMeta.isSuperClassEntity()) { 161 // only add the intercept and identity fields if 162 // the superClass is not also enhanced 163 if (isLog(4)) { 164 log("... add intercept and identity fields"); 165 } 166 InterceptField.addField(cv, classMeta, enhanceContext.isTransientInternalFields()); 167 MethodEquals.addIdentityField(cv, classMeta); 168 169 } 170 firstMethod = false; 171 } 172 173 if (isLog(4)) { 174 log("--- #### method name[" + name + "] desc[" + desc + "] sig[" + signature + "]"); 175 } 176 177 if (isConstructor(name, desc)) { 178 if (desc.equals(NOARG_VOID)) { 179 // ensure public access on the default constructor 180 access = Opcodes.ACC_PUBLIC; 181 } 182 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 183 return new ConstructorAdapter(mv, classMeta, desc); 184 } 185 186 if (isStaticInit(name, desc)) { 187 if (isLog(4)) { 188 log("... --- #### enhance existing static init method"); 189 } 190 MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, name, desc, signature, exceptions); 191 return new MethodStaticInitAdapter(mv, classMeta); 192 } 193 194 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 195 if (interceptEntityMethod(access, name, desc)) { 196 // change the method replacing the relevant GETFIELD PUTFIELD with 197 // our special field methods with interception... 198 return new MethodFieldAdapter(mv, classMeta, name + " " + desc); 199 } 200 // just leave as is, no interception etc 201 return mv; 202 } 203 204 /** 205 * Add methods to get and set the entityBeanIntercept. Also add the 206 * writeReplace method to control serialisation. 207 */ 208 @Override 209 public void visitEnd() { 210 if (!classMeta.isEntityEnhancementRequired()) { 211 throw new NoEnhancementRequiredException(); 212 } 213 if (!classMeta.hasStaticInit()) { 214 IndexFieldWeaver.addPropertiesInit(cv, classMeta); 215 } 216 if (!classMeta.hasDefaultConstructor()) { 217 DefaultConstructor.add(cv, classMeta); 218 } 219 if (isLog(4)) { 220 log("... add _ebean_getPropertyNames() and _ebean_getPropertyName()"); 221 } 222 IndexFieldWeaver.addGetPropertyNames(cv, classMeta); 223 IndexFieldWeaver.addGetPropertyName(cv, classMeta); 224 225 if (!classMeta.isSuperClassEntity()) { 226 if (isLog(8)) { 227 log("... add _ebean_getIntercept() and _ebean_setIntercept()"); 228 } 229 InterceptField.addGetterSetter(cv, classMeta); 230 } 231 232 // Add the field set/get methods which are used in place 233 // of GETFIELD PUTFIELD instructions 234 classMeta.addFieldGetSetMethods(cv); 235 236 //Add the getField(index) and setField(index) methods 237 IndexFieldWeaver.addMethods(cv, classMeta); 238 239 MethodSetEmbeddedLoaded.addMethod(cv, classMeta); 240 MethodIsEmbeddedNewOrDirty.addMethod(cv, classMeta); 241 MethodNewInstance.addMethod(cv, classMeta); 242 MethodNewInstanceReadOnly.interceptAddReadOnly(cv, classMeta); 243 244 // register with the agentContext 245 enhanceContext.addClassMeta(classMeta); 246 enhanceContext.summaryEntity(classMeta.getClassName()); 247 super.visitEnd(); 248 } 249 250 private boolean isConstructor(String name, String desc) { 251 if (name.equals(INIT)) { 252 if (desc.equals(NOARG_VOID)) { 253 classMeta.setHasDefaultConstructor(true); 254 } 255 return true; 256 } 257 return false; 258 } 259 260 private boolean isStaticInit(String name, String desc) { 261 if (name.equals(CLINIT) && desc.equals(NOARG_VOID)) { 262 classMeta.setHasStaticInit(true); 263 return true; 264 } 265 return false; 266 } 267 268 private boolean interceptEntityMethod(int access, String name, String desc) { 269 if ((access & Opcodes.ACC_STATIC) != 0) { 270 // no interception of static methods? 271 if (isLog(4)) { 272 log("Skip intercepting static method " + name); 273 } 274 return false; 275 } 276 if (name.equals("hashCode") && desc.equals("()I")) { 277 classMeta.setHasEqualsOrHashcode(true); 278 return true; 279 } 280 if (name.equals("equals") && desc.equals("(Ljava/lang/Object;)Z")) { 281 classMeta.setHasEqualsOrHashcode(true); 282 return true; 283 } 284 // don't intercept toString as its is used during debugging etc 285 return !name.equals("toString") || !desc.equals("()Ljava/lang/String;"); 286 } 287}