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 224 classMeta.addExistingMethod(name, desc); 225 226 if (isLog(4)) { 227 log("--- #### method name["+name+"] desc["+desc+"] sig["+signature+"]"); 228 } 229 230 if (isConstructor(name, desc)) { 231 if (desc.equals(NOARG_VOID)) { 232 // ensure public access on the default constructor 233 access = Opcodes.ACC_PUBLIC; 234 } 235 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 236 return new ConstructorAdapter(mv, classMeta, desc); 237 } 238 239 if (isStaticInit(name, desc)) { 240 if (isLog(4)) { 241 log("... --- #### enhance existing static init method"); 242 } 243 MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, name, desc, signature, exceptions); 244 return new MethodStaticInitAdapter(mv, classMeta); 245 } 246 247 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 248 249 if (interceptEntityMethod(access, name, desc)) { 250 // change the method replacing the relevant GETFIELD PUTFIELD with 251 // our special field methods with interception... 252 return new MethodFieldAdapter(mv, classMeta, name+" "+desc); 253 } 254 255 // just leave as is, no interception etc 256 return mv; 257 } 258 259 /** 260 * Add methods to get and set the entityBeanIntercept. Also add the 261 * writeReplace method to control serialisation. 262 */ 263 @Override 264 public void visitEnd() { 265 266 if (!classMeta.isEntityEnhancementRequired()){ 267 throw new NoEnhancementRequiredException(); 268 } 269 270 if (!classMeta.hasStaticInit()) { 271 IndexFieldWeaver.addPropertiesInit(cv, classMeta); 272 } 273 274 if (!classMeta.hasDefaultConstructor()){ 275 DefaultConstructor.add(cv, classMeta); 276 } 277 278 MarkerField.addGetMarker(cv, classMeta.getClassName()); 279 280 if (isLog(4)){ 281 log("... add _ebean_getPropertyNames() and _ebean_getPropertyName()"); 282 } 283 IndexFieldWeaver.addGetPropertyNames(cv, classMeta); 284 IndexFieldWeaver.addGetPropertyName(cv, classMeta); 285 286 if (!classMeta.isSuperClassEntity()){ 287 if (isLog(8)){ 288 log("... add _ebean_getIntercept() and _ebean_setIntercept()"); 289 } 290 InterceptField.addGetterSetter(cv, classMeta.getClassName()); 291 } 292 293 // Add the field set/get methods which are used in place 294 // of GETFIELD PUTFIELD instructions 295 classMeta.addFieldGetSetMethods(cv); 296 297 //Add the getField(index) and setField(index) methods 298 IndexFieldWeaver.addMethods(cv, classMeta); 299 300 MethodSetEmbeddedLoaded.addMethod(cv, classMeta); 301 MethodIsEmbeddedNewOrDirty.addMethod(cv, classMeta); 302 MethodNewInstance.addMethod(cv, classMeta); 303 304 // register with the agentContext 305 enhanceContext.addClassMeta(classMeta); 306 307 super.visitEnd(); 308 } 309 310 private boolean isConstructor(String name, String desc){ 311 312 if (name.equals(INIT)) { 313 if (desc.equals(NOARG_VOID)) { 314 classMeta.setHasDefaultConstructor(true); 315 } 316 return true; 317 } 318 return false; 319 } 320 321 private boolean isStaticInit(String name, String desc) { 322 323 if (name.equals(CLINIT) && desc.equals(NOARG_VOID)) { 324 classMeta.setHasStaticInit(true); 325 return true; 326 } 327 328 return false; 329 } 330 331 private boolean interceptEntityMethod(int access, String name, String desc) { 332 333 if ((access & Opcodes.ACC_STATIC) != 0) { 334 // no interception of static methods? 335 if (isLog(4)){ 336 log("Skip intercepting static method "+name); 337 } 338 return false; 339 } 340 341 if (name.equals("hashCode") && desc.equals("()I")) { 342 classMeta.setHasEqualsOrHashcode(true); 343 return true; 344 } 345 346 if (name.equals("equals") && desc.equals("(Ljava/lang/Object;)Z")) { 347 classMeta.setHasEqualsOrHashcode(true); 348 return true; 349 } 350 351 if (name.equals("toString") && desc.equals("()Ljava/lang/String;")) { 352 // don't intercept toString as its is used 353 // during debugging etc 354 return false; 355 } 356 357 return true; 358 } 359}