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