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.ASM6, 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 // this will hide the field on the super object... 176 // but being in a different ClassLoader means we don't 177 // get access to those 'real' private fields 178 FieldVisitor fv = super.visitField(access, name, desc, signature, value); 179 180 return classMeta.createLocalFieldVisitor(fv, name, desc); 181 } 182 183 /** 184 * Replace the method code with field interception. 185 */ 186 @Override 187 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 188 189 if (firstMethod){ 190 if (classMeta.isAlreadyEnhanced()) { 191 throw new NoEnhancementRequiredException(); 192 } 193 194 if (classMeta.hasEntityBeanInterface()){ 195 log("Enhancing when EntityBean interface already exists!"); 196 } 197 198 // always add the marker field on every enhanced class 199 String marker = MarkerField.addField(cv, classMeta.getClassName()); 200 if (isLog(4)){ 201 log("... add marker field \""+marker+"\""); 202 } 203 204 IndexFieldWeaver.addPropertiesField(cv); 205 if (isLog(4)){ 206 log("... add _ebean_props field"); 207 } 208 209 if (!classMeta.isSuperClassEntity()){ 210 // only add the intercept and identity fields if 211 // the superClass is not also enhanced 212 if (isLog(4)){ 213 log("... add intercept and identity fields"); 214 } 215 InterceptField.addField(cv, enhanceContext.isTransientInternalFields()); 216 MethodEquals.addIdentityField(cv); 217 218 } 219 firstMethod = false; 220 } 221 222 223 classMeta.addExistingMethod(name, desc); 224 225 if (isLog(4)) { 226 log("--- #### method name["+name+"] desc["+desc+"] sig["+signature+"]"); 227 } 228 229 if (isConstructor(name, desc)) { 230 if (desc.equals("()V")) { 231 // ensure public access on the default constructor 232 access = Opcodes.ACC_PUBLIC; 233 } 234 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 235 return new ConstructorAdapter(mv, classMeta, desc); 236 } 237 238 if (isStaticInit(name, desc)) { 239 if (isLog(4)) { 240 log("... --- #### enhance existing static init method"); 241 } 242 MethodVisitor mv = super.visitMethod(Opcodes.ACC_STATIC, name, desc, signature, exceptions); 243 return new MethodStaticInitAdapter(mv, classMeta); 244 } 245 246 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 247 248 if (interceptEntityMethod(access, name, desc)) { 249 // change the method replacing the relevant GETFIELD PUTFIELD with 250 // our special field methods with interception... 251 return new MethodFieldAdapter(mv, classMeta, name+" "+desc); 252 } 253 254 // just leave as is, no interception etc 255 return mv; 256 } 257 258 /** 259 * Add methods to get and set the entityBeanIntercept. Also add the 260 * writeReplace method to control serialisation. 261 */ 262 @Override 263 public void visitEnd() { 264 265 if (!classMeta.isEntityEnhancementRequired()){ 266 throw new NoEnhancementRequiredException(); 267 } 268 269 if (!classMeta.hasStaticInit()) { 270 IndexFieldWeaver.addPropertiesInit(cv, classMeta); 271 } 272 273 if (!classMeta.hasDefaultConstructor()){ 274 DefaultConstructor.add(cv, classMeta); 275 } 276 277 MarkerField.addGetMarker(cv, classMeta.getClassName()); 278 279 if (isLog(4)){ 280 log("... add _ebean_getPropertyNames() and _ebean_getPropertyName()"); 281 } 282 IndexFieldWeaver.addGetPropertyNames(cv, classMeta); 283 IndexFieldWeaver.addGetPropertyName(cv, classMeta); 284 285 if (!classMeta.isSuperClassEntity()){ 286 if (isLog(8)){ 287 log("... add _ebean_getIntercept() and _ebean_setIntercept()"); 288 } 289 InterceptField.addGetterSetter(cv, classMeta.getClassName()); 290 } 291 292 // Add the field set/get methods which are used in place 293 // of GETFIELD PUTFIELD instructions 294 classMeta.addFieldGetSetMethods(cv); 295 296 //Add the getField(index) and setField(index) methods 297 IndexFieldWeaver.addMethods(cv, classMeta); 298 299 MethodSetEmbeddedLoaded.addMethod(cv, classMeta); 300 MethodIsEmbeddedNewOrDirty.addMethod(cv, classMeta); 301 MethodNewInstance.addMethod(cv, classMeta); 302 303 // register with the agentContext 304 enhanceContext.addClassMeta(classMeta); 305 306 super.visitEnd(); 307 } 308 309 private boolean isConstructor(String name, String desc){ 310 311 if (name.equals("<init>")) { 312 if (desc.equals("()V")) { 313 classMeta.setHasDefaultConstructor(true); 314 } 315 return true; 316 } 317 return false; 318 } 319 320 private boolean isStaticInit(String name, String desc) { 321 322 if (name.equals("<clinit>") && desc.equals("()V")) { 323 classMeta.setHasStaticInit(true); 324 return true; 325 } 326 327 return false; 328 } 329 330 private boolean interceptEntityMethod(int access, String name, String desc) { 331 332 if ((access & Opcodes.ACC_STATIC) != 0) { 333 // no interception of static methods? 334 if (isLog(4)){ 335 log("Skip intercepting static method "+name); 336 } 337 return false; 338 } 339 340 if (name.equals("hashCode") && desc.equals("()I")) { 341 classMeta.setHasEqualsOrHashcode(true); 342 return true; 343 } 344 345 if (name.equals("equals") && desc.equals("(Ljava/lang/Object;)Z")) { 346 classMeta.setHasEqualsOrHashcode(true); 347 return true; 348 } 349 350 if (name.equals("toString") && desc.equals("()Ljava/lang/String;")) { 351 // don't intercept toString as its is used 352 // during debugging etc 353 return false; 354 } 355 356 return true; 357 } 358}