001package io.ebean.enhance.common;
002
003import io.ebean.enhance.Transformer;
004import io.ebean.enhance.entity.MessageOutput;
005
006import java.io.ByteArrayOutputStream;
007import java.io.PrintStream;
008import java.util.HashMap;
009import java.util.Set;
010import java.util.logging.Level;
011import java.util.logging.Logger;
012
013/**
014 * Used to hold meta data, arguments and log levels for the enhancement.
015 */
016public class EnhanceContext {
017
018  private static final Logger logger = Logger.getLogger(EnhanceContext.class.getName());
019
020  private final AgentManifest manifest;
021
022  private final IgnoreClassHelper ignoreClassHelper;
023
024  private final HashMap<String, String> agentArgsMap;
025
026  private final ClassMetaReader reader;
027
028  private final ClassBytesReader classBytesReader;
029
030  private MessageOutput logout;
031
032  private int logLevel;
033
034  private final HashMap<String, ClassMeta> map = new HashMap<>();
035
036  private final FilterEntityTransactional filterEntityTransactional;
037
038  private final FilterQueryBean filterQueryBean;
039
040  private boolean throwOnError;
041
042  private final boolean enableProfileLocation;
043  private final int accPublic;
044  private final int accProtected;
045  private final int accPrivate;
046  private final int ebeanInternalVersion;
047
048  private SummaryInfo summaryInfo;
049
050  public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest) {
051    this(classBytesReader, agentArgs, manifest, new ClassMetaCache());
052  }
053
054  /**
055   * Construct a context for enhancement.
056   */
057  public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest, ClassMetaCache metaCache) {
058    this.manifest = manifest;
059    this.enableProfileLocation = manifest.isEnableProfileLocation();
060    this.accPublic = manifest.accPublic();
061    this.accProtected = manifest.accProtected();
062    this.accPrivate = manifest.accPrivate();
063    this.ebeanInternalVersion = manifest.getEbeanInternalVersion();
064    this.agentArgsMap = ArgParser.parse(agentArgs);
065    this.filterEntityTransactional = new FilterEntityTransactional(manifest);
066    this.filterQueryBean = new FilterQueryBean(manifest);
067
068    this.ignoreClassHelper = new IgnoreClassHelper();
069    this.logout = new SysoutMessageOutput(System.out);
070    this.classBytesReader = classBytesReader;
071    this.reader = new ClassMetaReader(this, metaCache);
072
073    if (manifest.getDebugLevel() > -1) {
074      logLevel = manifest.getDebugLevel();
075    }
076
077    String debugValue = agentArgsMap.get("debug");
078    if (debugValue != null) {
079      try {
080        logLevel = Integer.parseInt(debugValue);
081      } catch (NumberFormatException e) {
082        logger.log(Level.WARNING, "Agent debug argument [" + debugValue + "] is not an int?");
083      }
084    }
085
086    if (getPropertyBoolean("printversion", false)) {
087      System.out.println("ebean agent version: " + Transformer.getVersion());
088    }
089  }
090
091  public void withClassLoader(ClassLoader loader) {
092    if (manifest.readManifest(loader)) {
093      if (logLevel > 1) {
094        log(null, "loaded entity packages: " + manifest.getEntityPackages());
095      }
096    }
097  }
098
099  public void setLogLevel(int logLevel) {
100    this.logLevel = logLevel;
101  }
102
103  /**
104   * Return the summary of the packages controlling enhancement.
105   */
106  public String getPackagesSummary() {
107    return "packages entity:" + getEntityPackages()
108      + "  transactional:" + getTransactionalPackages()
109      + "  querybean:" + getQuerybeanPackages()
110      + "  profileLocation:" + enableProfileLocation;
111  }
112
113  public Set<String> getEntityPackages() {
114    return manifest.getEntityPackages();
115  }
116
117  public Set<String> getTransactionalPackages() {
118    return manifest.getTransactionalPackages();
119  }
120
121  public Set<String> getQuerybeanPackages() {
122    return manifest.getQuerybeanPackages();
123  }
124
125  public byte[] getClassBytes(String className, ClassLoader classLoader) {
126    return classBytesReader.getClassBytes(className, classLoader);
127  }
128
129  /**
130   * Return true if the owner class is a type query bean.
131   * <p>
132   * If true typically means the caller needs to change GETFIELD calls to instead invoke the generated
133   * 'property access' methods.
134   * </p>
135   */
136  public boolean isQueryBean(String owner, ClassLoader classLoader) {
137    if (manifest.isDetectQueryBean(owner)) {
138      try {
139        final ClassMeta classMeta = reader.get(true, owner, classLoader);
140        if (classMeta == null) {
141          // For Gradle Kotlin KAPT the generate query bean bytecode
142          // isn't available to the classLoader. Just returning true.
143          return true;
144        }
145        return classMeta.isQueryBean();
146      } catch (ClassNotFoundException e) {
147        throw new RuntimeException(e);
148      }
149    }
150    return false;
151  }
152
153  /**
154   * Return a value from the entity arguments using its key.
155   */
156  private String getProperty(String key) {
157    return agentArgsMap.get(key.toLowerCase());
158  }
159
160  private boolean getPropertyBoolean(String key, boolean defaultValue) {
161    String s = getProperty(key);
162    if (s == null) {
163      return defaultValue;
164    } else {
165      return s.trim().equalsIgnoreCase("true");
166    }
167  }
168
169  /**
170   * Return true if profile location enhancement is on.
171   */
172  public boolean isEnableProfileLocation() {
173    return enableProfileLocation;
174  }
175
176  /**
177   * Return true if this class should be scanned for transactional enhancement.
178   */
179  public boolean detectEntityTransactionalEnhancement(String className) {
180    return filterEntityTransactional.detectEnhancement(className);
181  }
182
183  /**
184   * Return true if this class should be scanned for query bean enhancement.
185   */
186  public boolean detectQueryBeanEnhancement(String className) {
187    return filterQueryBean.detectEnhancement(className);
188  }
189
190  /**
191   * Return true if this class should be ignored. That is JDK classes and
192   * known libraries JDBC drivers etc can be skipped.
193   */
194  public boolean isIgnoreClass(String className) {
195    return ignoreClassHelper.isIgnoreClass(className);
196  }
197
198  /**
199   * Change the logout to something other than system out.
200   */
201  public void setLogout(MessageOutput logout) {
202    this.logout = logout;
203  }
204
205  /**
206   * Create a new meta object for enhancing a class.
207   */
208  public ClassMeta createClassMeta() {
209    return new ClassMeta(this, logLevel, logout);
210  }
211
212  /**
213   * Read the class meta data for a super class.
214   * <p>
215   * Typically used to read meta data for inheritance hierarchy.
216   * </p>
217   */
218  public ClassMeta getSuperMeta(String superClassName, ClassLoader classLoader) {
219
220    try {
221      if (isIgnoreClass(superClassName)) {
222        return null;
223      }
224      return reader.get(false, superClassName, classLoader);
225
226    } catch (ClassNotFoundException e) {
227      throw new RuntimeException(e);
228    }
229  }
230
231  /**
232   * Read the class meta data for an interface.
233   * <p>
234   * Typically used to check the interface to see if it is transactional.
235   * </p>
236   */
237  public ClassMeta getInterfaceMeta(String interfaceClassName, ClassLoader classLoader) {
238
239    try {
240      if (isIgnoreClass(interfaceClassName)) {
241        return null;
242      }
243      return reader.get(true, interfaceClassName, classLoader);
244
245    } catch (ClassNotFoundException e) {
246      throw new RuntimeException(e);
247    }
248  }
249
250  public void addClassMeta(ClassMeta meta) {
251    map.put(meta.getClassName(), meta);
252  }
253
254  public ClassMeta get(String className) {
255    return map.get(className);
256  }
257
258  /**
259   * Log some debug output.
260   */
261  public void log(int level, String className, String msg) {
262    if (logLevel >= level) {
263      log(className, msg);
264    }
265  }
266
267  public void log(String className, String msg) {
268    if (className != null) {
269      msg = "cls: " + className + "  msg: " + msg;
270    }
271    logout.println("ebean-enhance> " + msg);
272  }
273
274  public boolean isLog(int level) {
275    return logLevel >= level;
276  }
277
278  /**
279   * Log an error.
280   */
281  public void log(Throwable e) {
282    e.printStackTrace(
283      new PrintStream(new ByteArrayOutputStream()) {
284        @Override
285        public void print(String message) {
286          logout.println(message);
287        }
288
289        @Override
290        public void println(String message) {
291          logout.println(message);
292        }
293      });
294  }
295
296
297  /**
298   * Return the log level.
299   */
300  public int getLogLevel() {
301    return logLevel;
302  }
303
304  /**
305   * Return true if internal ebean fields in entity classes should be transient.
306   */
307  public boolean isTransientInternalFields() {
308    return manifest.isTransientInternalFields();
309  }
310
311  /**
312   * Return true if we should add null checking on *ToMany fields.
313   * <p>
314   * On getting a many that is null Ebean will create an empty List, Set or Map. If it is a
315   * ManyToMany it will turn on Modify listening.
316   * </p>
317   */
318  public boolean isCheckNullManyFields() {
319    return manifest.isCheckNullManyFields();
320  }
321
322  /**
323   * Return true if transform should throw exception rather than log and return null.
324   */
325  public boolean isThrowOnError() {
326    return throwOnError;
327  }
328
329  /**
330   * Set to true if you want transform to throw exceptions rather than return null.
331   */
332  public void setThrowOnError(boolean throwOnError) {
333    this.throwOnError = throwOnError;
334  }
335
336  /**
337   * Turn on the summary collection of the enhancement.
338   */
339  public void collectSummary() {
340    this.summaryInfo = new SummaryInfo(manifest.getLoadedResources());
341  }
342
343  /**
344   * Add the transactional enhanced class to summary information.
345   */
346  public void summaryTransactional(String className) {
347    if (summaryInfo != null) {
348      summaryInfo.addTransactional(className);
349    }
350  }
351
352  /**
353   * Add the entity enhanced class to summary information.
354   */
355  public void summaryEntity(String className) {
356    if (summaryInfo != null) {
357      summaryInfo.addEntity(className);
358    }
359  }
360
361  /**
362   * Add the query bean enhanced class to summary information.
363   */
364  public void summaryQueryBean(String className) {
365    if (summaryInfo != null) {
366      summaryInfo.addQueryBean(className);
367    }
368  }
369
370  /**
371   * Add the query bean caller enhanced class to summary information.
372   */
373  public void summaryQueryBeanCaller(String className) {
374    if (summaryInfo != null) {
375      summaryInfo.addQueryBeanCaller(className);
376    }
377  }
378
379  /**
380   * Return the summary of the enhancement.
381   * <p>
382   * Note that <code>collectSummary()</code> must be called in order for summary
383   * information to be collected and returned here.
384   */
385  public SummaryInfo getSummaryInfo() {
386    return summaryInfo.prepare();
387  }
388
389  public int accPublic() {
390    return accPublic;
391  }
392
393  public int accProtected() {
394    return accProtected;
395  }
396
397  public int accPrivate() {
398    return accPrivate;
399  }
400
401  public boolean isToManyGetField() {
402    return ebeanInternalVersion > 128;
403  }
404}