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