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