001package io.ebean.enhance;
002
003import io.ebean.enhance.asm.ClassReader;
004import io.ebean.enhance.asm.ClassWriter;
005import io.ebean.enhance.asm.ClassWriterWithoutClassLoading;
006import io.ebean.enhance.common.AgentManifest;
007import io.ebean.enhance.common.AlreadyEnhancedException;
008import io.ebean.enhance.common.ClassBytesReader;
009import io.ebean.enhance.common.CommonSuperUnresolved;
010import io.ebean.enhance.common.DetectEnhancement;
011import io.ebean.enhance.common.EnhanceConstants;
012import io.ebean.enhance.common.EnhanceContext;
013import io.ebean.enhance.common.NoEnhancementRequiredException;
014import io.ebean.enhance.common.TransformRequest;
015import io.ebean.enhance.common.UrlPathHelper;
016import io.ebean.enhance.entity.ClassAdapterEntity;
017import io.ebean.enhance.entity.ClassPathClassBytesReader;
018import io.ebean.enhance.entity.MessageOutput;
019import io.ebean.enhance.querybean.TypeQueryClassAdapter;
020import io.ebean.enhance.transactional.ClassAdapterTransactional;
021import io.ebean.enhance.transactional.TransactionalMethodKey;
022
023import java.lang.instrument.ClassFileTransformer;
024import java.lang.instrument.IllegalClassFormatException;
025import java.lang.instrument.Instrumentation;
026import java.net.URL;
027import java.security.ProtectionDomain;
028import java.util.ArrayList;
029import java.util.List;
030
031/**
032 * A Class file Transformer that performs Ebean enhancement of entity beans,
033 * transactional methods and query bean enhancement.
034 * <p>
035 * This is used as both a java agent or via Maven and Gradle plugins etc.
036 * </p>
037 */
038public class Transformer implements ClassFileTransformer {
039
040  public static void premain(String agentArgs, Instrumentation inst) {
041
042    Transformer transformer = new Transformer(null, agentArgs);
043    inst.addTransformer(transformer);
044  }
045
046  public static void agentmain(String agentArgs, Instrumentation inst) throws Exception {
047    premain(agentArgs, inst);
048  }
049
050  private final EnhanceContext enhanceContext;
051
052  private final List<CommonSuperUnresolved> unresolved = new ArrayList<>();
053
054  private boolean keepUnresolved;
055
056  public Transformer(ClassLoader classLoader, String agentArgs) {
057    if (classLoader == null) {
058      classLoader = getClass().getClassLoader();
059    }
060    ClassBytesReader reader = new ClassPathClassBytesReader(null);
061    AgentManifest manifest = AgentManifest.read(classLoader, null);
062    this.enhanceContext = new EnhanceContext(reader, agentArgs, manifest);
063  }
064
065  /**
066   * Create a transformer for entity bean enhancement and transactional method enhancement.
067   *
068   * @param bytesReader reads resources from class path for related inheritance and interfaces
069   * @param agentArgs command line arguments for debug level etc
070   */
071  public Transformer(ClassBytesReader bytesReader, String agentArgs, AgentManifest manifest) {
072    this.enhanceContext = new EnhanceContext(bytesReader, agentArgs, manifest);
073  }
074
075  /**
076   * Set this to keep and report unresolved explicitly.
077   */
078  public void setKeepUnresolved() {
079    this.keepUnresolved = true;
080  }
081
082  /**
083   * Change the logout to something other than system out.
084   */
085  public void setLogout(MessageOutput logout) {
086    this.enhanceContext.setLogout(logout);
087  }
088
089  public void log(int level, String msg) {
090    log(level, null, msg);
091  }
092
093  private void log(int level, String className, String msg) {
094    enhanceContext.log(level, className, msg);
095  }
096
097  public int getLogLevel() {
098    return enhanceContext.getLogLevel();
099  }
100
101  public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
102
103    try {
104      // ignore JDK and JDBC classes etc
105      if (enhanceContext.isIgnoreClass(className)) {
106        log(9, className, "ignore class");
107        return null;
108      }
109      TransformRequest request = new TransformRequest(classfileBuffer);
110
111      boolean isEbeanModel = className.equals(EnhanceConstants.EBEAN_MODEL);
112      if (isEbeanModel || enhanceContext.detectEntityTransactionalEnhancement(className)) {
113
114        DetectEnhancement detect = detect(loader, classfileBuffer);
115
116        if (detect.isEntity()) {
117          if (detect.isEnhancedEntity()) {
118            detect.log(3, "already enhanced entity");
119          } else {
120            entityEnhancement(loader, request);
121          }
122        }
123
124        if (detect.isTransactional()) {
125          if (detect.isEnhancedTransactional()) {
126            detect.log(3, "already enhanced transactional");
127          } else {
128            transactionalEnhancement(loader, request);
129          }
130        }
131      }
132
133      if (enhanceContext.detectQueryBeanEnhancement(className)) {
134        enhanceQueryBean(loader, request);
135      }
136
137      if (request.isEnhanced()) {
138        return request.getBytes();
139      }
140
141      log(9, className, "no enhancement on class");
142      return null;
143
144    } catch (NoEnhancementRequiredException e) {
145      // the class is an interface
146      log(8, className, "No Enhancement required " + e.getMessage());
147      return null;
148
149    } catch (Exception e) {
150      enhanceContext.log(e);
151      return null;
152    } finally {
153      logUnresolvedCommonSuper(className);
154    }
155  }
156
157  /**
158   * Return the transaction profiling keys.
159   *
160   * We use these to decode a the transaction profile.
161   */
162  public List<TransactionalMethodKey> getTransactionProfilingKeys() {
163    return enhanceContext.getTransactionProfilingKeys();
164  }
165
166  /**
167   * Log and common superclass classpath issues that defaulted to Object.
168   */
169  private void logUnresolvedCommonSuper(String className) {
170    if (!keepUnresolved && !unresolved.isEmpty()) {
171      for (CommonSuperUnresolved commonUnresolved : unresolved) {
172        log(0, className, commonUnresolved.getMessage());
173      }
174      unresolved.clear();
175    }
176  }
177
178  /**
179   * Return the list of unresolved common superclass issues. This should be cleared
180   * after each use and can only be used with {@link #setKeepUnresolved()}.
181   */
182  public List<CommonSuperUnresolved> getUnresolved() {
183    return unresolved;
184  }
185
186  /**
187   * Perform entity bean enhancement.
188   */
189  private void entityEnhancement(ClassLoader loader, TransformRequest request) {
190
191    ClassReader cr = new ClassReader(request.getBytes());
192    ClassWriter cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader);
193    ClassAdapterEntity ca = new ClassAdapterEntity(cw, loader, enhanceContext);
194    try {
195
196      cr.accept(ca, ClassReader.EXPAND_FRAMES);
197
198      if (ca.isLog(1)) {
199        ca.logEnhanced();
200      }
201
202      request.enhancedEntity(cw.toByteArray());
203
204    } catch (AlreadyEnhancedException e) {
205      if (ca.isLog(1)) {
206        ca.log("already enhanced entity");
207      }
208      request.enhancedEntity(null);
209
210    } catch (NoEnhancementRequiredException e) {
211      if (ca.isLog(2)) {
212        ca.log("skipping... no enhancement required");
213      }
214    } finally {
215      unresolved.addAll(cw.getUnresolved());
216    }
217  }
218
219  /**
220   * Perform transactional enhancement.
221   */
222  private void transactionalEnhancement(ClassLoader loader, TransformRequest request) {
223
224    ClassReader cr = new ClassReader(request.getBytes());
225    ClassWriter cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader);
226    ClassAdapterTransactional ca = new ClassAdapterTransactional(cw, loader, enhanceContext);
227
228    try {
229      cr.accept(ca, ClassReader.EXPAND_FRAMES);
230
231      if (ca.isLog(1)) {
232        ca.log("enhanced transactional");
233      }
234
235      request.enhancedTransactional(cw.toByteArray());
236
237    } catch (AlreadyEnhancedException e) {
238      if (ca.isLog(1)) {
239        ca.log("already enhanced");
240      }
241
242    } catch (NoEnhancementRequiredException e) {
243      if (ca.isLog(0)) {
244        ca.log("skipping... no enhancement required");
245      }
246    } finally {
247      unresolved.addAll(cw.getUnresolved());
248    }
249  }
250
251
252  /**
253   * Perform enhancement.
254   */
255  private void enhanceQueryBean(ClassLoader loader, TransformRequest request) {
256
257    ClassReader cr = new ClassReader(request.getBytes());
258    ClassWriter cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader);
259    TypeQueryClassAdapter ca = new TypeQueryClassAdapter(cw, enhanceContext);
260
261    try {
262      cr.accept(ca, ClassReader.EXPAND_FRAMES);
263      if (ca.isLog(9)) {
264        ca.log("... completed");
265      }
266      request.enhancedQueryBean(cw.toByteArray());
267
268    } catch (AlreadyEnhancedException e) {
269      if (ca.isLog(1)) {
270        ca.log("already enhanced");
271      }
272
273    } catch (NoEnhancementRequiredException e) {
274      if (ca.isLog(9)) {
275        ca.log("... skipping, no enhancement required");
276      }
277    } finally {
278      unresolved.addAll(cw.getUnresolved());
279    }
280  }
281
282  /**
283   * Helper method to split semi-colon separated class paths into a URL array.
284   */
285  public static URL[] parseClassPaths(String extraClassPath) {
286
287    if (extraClassPath == null) {
288      return new URL[0];
289    }
290
291    return UrlPathHelper.convertToUrl(extraClassPath.split(";"));
292  }
293
294  /**
295   * Read the bytes quickly trying to detect if it needs entity or transactional
296   * enhancement.
297   */
298  private DetectEnhancement detect(ClassLoader classLoader, byte[] classfileBuffer) {
299
300    DetectEnhancement detect = new DetectEnhancement(classLoader, enhanceContext);
301
302    ClassReader cr = new ClassReader(classfileBuffer);
303    cr.accept(detect, ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES);
304    return detect;
305  }
306}