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