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  /**
053   * Mapping of profileId to transactional method descriptions (for decoding profiling).
054   */
055  private final List<TransactionalMethodKey> profilingKeys = new ArrayList();
056
057  /**
058   * Construct a context for enhancement.
059   */
060  public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest) {
061
062    this.autoProfileId = manifest.transactionProfilingStart();
063
064    this.agentArgsMap = ArgParser.parse(agentArgs);
065    this.filterEntityTransactional = new FilterEntityTransactional(manifest);
066    this.filterQueryBean = new FilterQueryBean(manifest);
067
068    this.detectQueryBean = Distill.convert(manifest.getEntityPackages());
069    if (detectQueryBean.isEmpty()) {
070      logger.log(Level.FINE, "No ebean.mf detected");
071    }
072
073    this.ignoreClassHelper = new IgnoreClassHelper();
074    this.logout = new SysoutMessageOutput(System.out);
075    this.classBytesReader = classBytesReader;
076    this.reader = new ClassMetaReader(this);
077
078    String debugValue = agentArgsMap.get("debug");
079    if (debugValue != null) {
080      try {
081        logLevel = Integer.parseInt(debugValue);
082      } catch (NumberFormatException e) {
083        logger.log(Level.WARNING, "Agent debug argument [" + debugValue + "] is not an int?");
084      }
085    }
086
087    this.transientInternalFields = getPropertyBoolean("transientInternalFields", manifest.isTransientInternalFields());
088    this.checkNullManyFields = getPropertyBoolean("checkNullManyFields", manifest.isCheckNullManyFields());
089  }
090
091  public byte[] getClassBytes(String className, ClassLoader classLoader) {
092    return classBytesReader.getClassBytes(className, classLoader);
093  }
094
095  /**
096   * Return true if the owner class is a type query bean.
097   * <p>
098   * If true typically means the caller needs to change GETFIELD calls to instead invoke the generated
099   * 'property access' methods.
100   * </p>
101   */
102  public boolean isQueryBean(String owner) {
103    return detectQueryBean.isQueryBean(owner);
104  }
105
106  /**
107   * Return a value from the entity arguments using its key.
108   */
109  public String getProperty(String key) {
110    return agentArgsMap.get(key.toLowerCase());
111  }
112
113  public boolean getPropertyBoolean(String key, boolean defaultValue) {
114    String s = getProperty(key);
115    if (s == null) {
116      return defaultValue;
117    } else {
118      return s.trim().equalsIgnoreCase("true");
119    }
120  }
121
122  /**
123   * Return true if this class should be scanned for transactional enhancement.
124   */
125  public boolean detectEntityTransactionalEnhancement(String className) {
126    return filterEntityTransactional.detectEnhancement(className);
127  }
128
129  /**
130   * Return true if this class should be scanned for query bean enhancement.
131   */
132  public boolean detectQueryBeanEnhancement(String className) {
133    return filterQueryBean.detectEnhancement(className);
134  }
135
136  /**
137   * Return true if this class should be ignored. That is JDK classes and
138   * known libraries JDBC drivers etc can be skipped.
139   */
140  public boolean isIgnoreClass(String className) {
141    return ignoreClassHelper.isIgnoreClass(className);
142  }
143
144  /**
145   * Change the logout to something other than system out.
146   */
147  public void setLogout(MessageOutput logout) {
148    this.logout = logout;
149  }
150
151  /**
152   * Create a new meta object for enhancing a class.
153   */
154  public ClassMeta createClassMeta() {
155    return new ClassMeta(this, logLevel, logout);
156  }
157
158  /**
159   * Read the class meta data for a super class.
160   * <p>
161   * Typically used to read meta data for inheritance hierarchy.
162   * </p>
163   */
164  public ClassMeta getSuperMeta(String superClassName, ClassLoader classLoader) {
165
166    try {
167      if (isIgnoreClass(superClassName)) {
168        return null;
169      }
170      return reader.get(false, superClassName, classLoader);
171
172    } catch (ClassNotFoundException e) {
173      throw new RuntimeException(e);
174    }
175  }
176
177  /**
178   * Read the class meta data for an interface.
179   * <p>
180   * Typically used to check the interface to see if it is transactional.
181   * </p>
182   */
183  public ClassMeta getInterfaceMeta(String interfaceClassName, ClassLoader classLoader) {
184
185    try {
186      if (isIgnoreClass(interfaceClassName)) {
187        return null;
188      }
189      return reader.get(true, interfaceClassName, classLoader);
190
191    } catch (ClassNotFoundException e) {
192      throw new RuntimeException(e);
193    }
194  }
195
196  public void addClassMeta(ClassMeta meta) {
197    map.put(meta.getClassName(), meta);
198  }
199
200  public ClassMeta get(String className) {
201    return map.get(className);
202  }
203
204  /**
205   * Log some debug output.
206   */
207  public void log(int level, String className, String msg) {
208    if (logLevel >= level) {
209      log(className, msg);
210    }
211  }
212
213  public void log(String className, String msg) {
214    if (className != null) {
215      msg = "cls: " + className + "  msg: " + msg;
216    }
217    logout.println("ebean-enhance> " + msg);
218  }
219
220  public boolean isLog(int level) {
221    return logLevel >= level;
222  }
223
224  /**
225   * Log an error.
226   */
227  public void log(Throwable e) {
228    e.printStackTrace(
229        new PrintStream(new ByteArrayOutputStream()) {
230          @Override
231          public void print(String message) {
232            logout.println(message);
233          }
234
235          @Override
236          public void println(String message) {
237            logout.println(message);
238          }
239        });
240  }
241
242
243  /**
244   * Return the log level.
245   */
246  public int getLogLevel() {
247    return logLevel;
248  }
249
250  /**
251   * Return true if internal ebean fields in entity classes should be transient.
252   */
253  public boolean isTransientInternalFields() {
254    return transientInternalFields;
255  }
256
257  /**
258   * Return true if we should add null checking on *ToMany fields.
259   * <p>
260   * On getting a many that is null Ebean will create an empty List, Set or Map. If it is a
261   * ManyToMany it will turn on Modify listening.
262   * </p>
263   */
264  public boolean isCheckNullManyFields() {
265    return checkNullManyFields;
266  }
267
268  /**
269   * Create a TransactionalMethodKey with (maybe) a profileId.
270   */
271  public TransactionalMethodKey createMethodKey(String className, String methodName, String methodDesc, int profileId) {
272
273    TransactionalMethodKey key = new TransactionalMethodKey(className, methodName, methodDesc);
274
275    if (autoProfileId == -1) {
276      // disabled (including disabling profileIds on @Transactional)
277      key.setProfileId(0);
278    } else {
279      if (profileId == 0 && autoProfileId > 0) {
280        // enabled mode automatically setting to the next profileId
281        profileId = ++autoProfileId;
282      }
283      key.setProfileId(profileId);
284      if (profileId > 0) {
285        // we are only interested in the profiling transactions
286        profilingKeys.add(key);
287      }
288    }
289
290    return key;
291  }
292
293  /**
294   * Return the profiling transaction keys.
295   */
296  public List<TransactionalMethodKey> getTransactionProfilingKeys() {
297    return profilingKeys;
298  }
299}