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