001package io.ebean.enhance.common;
002
003import io.ebean.enhance.Transformer;
004import io.ebean.enhance.entity.MessageOutput;
005import io.ebean.enhance.querybean.DetectQueryBean;
006import io.ebean.enhance.querybean.Distill;
007import io.ebean.enhance.transactional.TransactionalMethodKey;
008
009import java.io.ByteArrayOutputStream;
010import java.io.PrintStream;
011import java.util.ArrayList;
012import java.util.HashMap;
013import java.util.List;
014import java.util.logging.Level;
015import java.util.logging.Logger;
016
017/**
018 * Used to hold meta data, arguments and log levels for the enhancement.
019 */
020public class EnhanceContext {
021
022  private static final Logger logger = Logger.getLogger(EnhanceContext.class.getName());
023
024  private final IgnoreClassHelper ignoreClassHelper;
025
026  private final HashMap<String, String> agentArgsMap;
027
028  private final boolean transientInternalFields;
029
030  private final boolean checkNullManyFields;
031
032  private final ClassMetaReader reader;
033
034  private final ClassBytesReader classBytesReader;
035
036  private MessageOutput logout;
037
038  private int logLevel;
039
040  private final HashMap<String, ClassMeta> map = new HashMap<>();
041
042  private final DetectQueryBean detectQueryBean;
043
044  private final FilterEntityTransactional filterEntityTransactional;
045
046  private final FilterQueryBean filterQueryBean;
047
048  /**
049  * Current profileId when automatically assigned.
050  */
051  private int autoProfileId;
052
053  private boolean throwOnError;
054
055  private final boolean enableProfileLocation;
056
057  private final boolean enableQueryAutoLabel;
058
059  /**
060  * Mapping of profileId to transactional method descriptions (for decoding profiling).
061  */
062  private final List<TransactionalMethodKey> profilingKeys = new ArrayList<>();
063
064  public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest) {
065    this(classBytesReader, agentArgs, manifest, new ClassMetaCache());
066  }
067
068  /**
069  * Construct a context for enhancement.
070  */
071  public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest, ClassMetaCache metaCache) {
072
073    this.autoProfileId = manifest.transactionProfilingStart();
074    this.enableProfileLocation = manifest.isEnableProfileLocation();
075    this.enableQueryAutoLabel = manifest.isEnableQueryAutoLabel();
076
077    this.agentArgsMap = ArgParser.parse(agentArgs);
078    this.filterEntityTransactional = new FilterEntityTransactional(manifest);
079    this.filterQueryBean = new FilterQueryBean(manifest);
080
081    this.detectQueryBean = Distill.convert(manifest.getEntityPackages());
082    if (detectQueryBean.isEmpty()) {
083      logger.log(Level.FINE, "No ebean.mf detected");
084    }
085
086    this.ignoreClassHelper = new IgnoreClassHelper();
087    this.logout = new SysoutMessageOutput(System.out);
088    this.classBytesReader = classBytesReader;
089    this.reader = new ClassMetaReader(this, metaCache);
090
091    if (manifest.getDebugLevel() > -1) {
092      logLevel = manifest.getDebugLevel();
093    }
094
095    String debugValue = agentArgsMap.get("debug");
096    if (debugValue != null) {
097      try {
098        logLevel = Integer.parseInt(debugValue);
099      } catch (NumberFormatException e) {
100        logger.log(Level.WARNING, "Agent debug argument [" + debugValue + "] is not an int?");
101      }
102    }
103
104    this.transientInternalFields = getPropertyBoolean("transientInternalFields", manifest.isTransientInternalFields());
105    this.checkNullManyFields = getPropertyBoolean("checkNullManyFields", manifest.isCheckNullManyFields());
106    if (getPropertyBoolean("printversion", false)) {
107      System.out.println("ebean agent version: " + Transformer.getVersion());
108    }
109  }
110
111  public byte[] getClassBytes(String className, ClassLoader classLoader) {
112    return classBytesReader.getClassBytes(className, classLoader);
113  }
114
115  /**
116  * Return true if the owner class is a type query bean.
117  * <p>
118  * If true typically means the caller needs to change GETFIELD calls to instead invoke the generated
119  * 'property access' methods.
120  * </p>
121  */
122  public boolean isQueryBean(String owner, ClassLoader classLoader) {
123    if (detectQueryBean.isQueryBean(owner)) {
124      try {
125        final ClassMeta classMeta = reader.get(true, owner, classLoader);
126        return classMeta.isQueryBean();
127      } catch (ClassNotFoundException e) {
128        throw new RuntimeException(e);
129      }
130    }
131    return false;
132  }
133
134  /**
135  * Return a value from the entity arguments using its key.
136  */
137  private String getProperty(String key) {
138    return agentArgsMap.get(key.toLowerCase());
139  }
140
141  private boolean getPropertyBoolean(String key, boolean defaultValue) {
142    String s = getProperty(key);
143    if (s == null) {
144      return defaultValue;
145    } else {
146      return s.trim().equalsIgnoreCase("true");
147    }
148  }
149
150  /**
151  * Return true if profile location enhancement is on.
152  */
153  public boolean isEnableProfileLocation() {
154    return enableProfileLocation;
155  }
156
157  /**
158   * Return true if enhancement should automatically set labels on queries.
159   */
160  public boolean isEnableQueryAutoLabel() {
161    return enableQueryAutoLabel;
162  }
163
164  /**
165  * Return true if this class should be scanned for transactional enhancement.
166  */
167  public boolean detectEntityTransactionalEnhancement(String className) {
168    return filterEntityTransactional.detectEnhancement(className);
169  }
170
171  /**
172  * Return true if this class should be scanned for query bean enhancement.
173  */
174  public boolean detectQueryBeanEnhancement(String className) {
175    return filterQueryBean.detectEnhancement(className);
176  }
177
178  /**
179  * Return true if this class should be ignored. That is JDK classes and
180  * known libraries JDBC drivers etc can be skipped.
181  */
182  public boolean isIgnoreClass(String className) {
183    return ignoreClassHelper.isIgnoreClass(className);
184  }
185
186  /**
187  * Change the logout to something other than system out.
188  */
189  public void setLogout(MessageOutput logout) {
190    this.logout = logout;
191  }
192
193  /**
194  * Create a new meta object for enhancing a class.
195  */
196  public ClassMeta createClassMeta() {
197    return new ClassMeta(this, logLevel, logout);
198  }
199
200  /**
201  * Read the class meta data for a super class.
202  * <p>
203  * Typically used to read meta data for inheritance hierarchy.
204  * </p>
205  */
206  public ClassMeta getSuperMeta(String superClassName, ClassLoader classLoader) {
207
208    try {
209      if (isIgnoreClass(superClassName)) {
210        return null;
211      }
212      return reader.get(false, superClassName, classLoader);
213
214    } catch (ClassNotFoundException e) {
215      throw new RuntimeException(e);
216    }
217  }
218
219  /**
220  * Read the class meta data for an interface.
221  * <p>
222  * Typically used to check the interface to see if it is transactional.
223  * </p>
224  */
225  public ClassMeta getInterfaceMeta(String interfaceClassName, ClassLoader classLoader) {
226
227    try {
228      if (isIgnoreClass(interfaceClassName)) {
229        return null;
230      }
231      return reader.get(true, interfaceClassName, classLoader);
232
233    } catch (ClassNotFoundException e) {
234      throw new RuntimeException(e);
235    }
236  }
237
238  public void addClassMeta(ClassMeta meta) {
239    map.put(meta.getClassName(), meta);
240  }
241
242  public ClassMeta get(String className) {
243    return map.get(className);
244  }
245
246  /**
247  * Log some debug output.
248  */
249  public void log(int level, String className, String msg) {
250    if (logLevel >= level) {
251      log(className, msg);
252    }
253  }
254
255  public void log(String className, String msg) {
256    if (className != null) {
257      msg = "cls: " + className + "  msg: " + msg;
258    }
259    logout.println("ebean-enhance> " + msg);
260  }
261
262  public boolean isLog(int level) {
263    return logLevel >= level;
264  }
265
266  /**
267  * Log an error.
268  */
269  public void log(Throwable e) {
270    e.printStackTrace(
271        new PrintStream(new ByteArrayOutputStream()) {
272          @Override
273          public void print(String message) {
274            logout.println(message);
275          }
276
277          @Override
278          public void println(String message) {
279            logout.println(message);
280          }
281        });
282  }
283
284
285  /**
286  * Return the log level.
287  */
288  public int getLogLevel() {
289    return logLevel;
290  }
291
292  /**
293  * Return true if internal ebean fields in entity classes should be transient.
294  */
295  public boolean isTransientInternalFields() {
296    return transientInternalFields;
297  }
298
299  /**
300  * Return true if we should add null checking on *ToMany fields.
301  * <p>
302  * On getting a many that is null Ebean will create an empty List, Set or Map. If it is a
303  * ManyToMany it will turn on Modify listening.
304  * </p>
305  */
306  public boolean isCheckNullManyFields() {
307    return checkNullManyFields;
308  }
309
310  /**
311  * Create a TransactionalMethodKey with (maybe) a profileId.
312  */
313  public TransactionalMethodKey createMethodKey(String className, String methodName, String methodDesc, int profileId) {
314
315    TransactionalMethodKey key = new TransactionalMethodKey(className, methodName, methodDesc);
316
317    if (autoProfileId == -1) {
318      // disabled (including disabling profileIds on @Transactional)
319      key.setProfileId(0);
320    } else {
321      if (profileId == 0 && autoProfileId > 0) {
322        // enabled mode automatically setting to the next profileId
323        profileId = ++autoProfileId;
324      }
325      key.setProfileId(profileId);
326      if (profileId > 0) {
327        // we are only interested in the profiling transactions
328        profilingKeys.add(key);
329      }
330    }
331
332    return key;
333  }
334
335  /**
336  * Return the profiling transaction keys.
337  */
338  public List<TransactionalMethodKey> getTransactionProfilingKeys() {
339    return profilingKeys;
340  }
341
342  /**
343  * Return true if transform should throw exception rather than log and return null.
344  */
345  public boolean isThrowOnError() {
346    return throwOnError;
347  }
348
349  /**
350  * Set to true if you want transform to throw exceptions rather than return null.
351  */
352  public void setThrowOnError(boolean throwOnError) {
353    this.throwOnError = throwOnError;
354  }
355}