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}