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