001package io.ebean.enhance.common;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.net.URL;
006import java.util.Enumeration;
007import java.util.HashSet;
008import java.util.Set;
009import java.util.jar.Attributes;
010import java.util.jar.Manifest;
011
012/**
013 * Reads all the META-INF/ebean.mf and META-INF/ebean-typequery.mf resources with the locations
014 * of all the entity beans (and hence locations of query beans).
015 */
016public class AgentManifest {
017
018  enum TxProfileMode {
019    NONE,
020    ENABLED,
021    MANUAL
022  }
023
024  private final Set<String> entityPackages = new HashSet<>();
025
026  private final Set<String> transactionalPackages = new HashSet<>();
027
028  private final Set<String> querybeanPackages = new HashSet<>();
029
030  private TxProfileMode transactionProfilingMode = TxProfileMode.NONE;
031
032  /**
033  * Start profileId when automatically assigned by enhancement.
034  */
035  private int transactionProfilingStart = 1000;
036
037  private int debugLevel = -1;
038
039  private boolean transientInternalFields;
040
041  private boolean checkNullManyFields = true;
042
043  private boolean enableProfileLocation;
044
045  private boolean enableQueryAutoLabel;
046
047  public static AgentManifest read(ClassLoader classLoader, Set<String> initialPackages) {
048
049    try {
050      return new AgentManifest(initialPackages)
051          .readManifests(classLoader, "META-INF/ebean-typequery.mf")
052          .readManifests(classLoader, "META-INF/ebean.mf")
053          .readManifests(classLoader, "ebean.mf");
054
055    } catch (IOException e) {
056      // log to standard error and return empty
057      System.err.println("Agent: error reading ebean manifest resources");
058      e.printStackTrace();
059      return new AgentManifest();
060    }
061  }
062
063  /**
064  * Construct with some packages defined externally.
065  */
066  public AgentManifest(Set<String> initialPackages) {
067    if (initialPackages != null) {
068      entityPackages.addAll(initialPackages);
069    }
070  }
071
072  /**
073  * Construct with no initial packages (to use with addRaw()).
074  */
075  public AgentManifest() {
076  }
077
078  @Override
079  public String toString() {
080    return "entityPackages:" + entityPackages + " querybeanPackages:" + querybeanPackages
081      + " transactionalPackages:" + transactionalPackages + " profilingMode:" + transactionProfilingMode;
082  }
083
084  /**
085  * Return true if enhancement of profileLocations should be added.
086  */
087  public boolean isEnableProfileLocation() {
088    return enableProfileLocation;
089  }
090
091  /**
092   * Return true if enhancement should add labels to query bean queries.
093   */
094  public boolean isEnableQueryAutoLabel() {
095    return enableQueryAutoLabel;
096  }
097
098  /**
099  * Return the initial starting profileId when automatically assigned.
100  */
101  int transactionProfilingStart() {
102    switch (transactionProfilingMode) {
103      case NONE:
104        return -1;
105      case MANUAL:
106        return 0;
107      case ENABLED:
108        return transactionProfilingStart;
109      default: {
110        return transactionProfilingStart;
111      }
112    }
113  }
114
115  /**
116   * Return the debug level read from ebean.mf
117   */
118  public int getDebugLevel() {
119    return debugLevel;
120  }
121
122  /**
123  * Return the parsed set of packages that type query beans are in.
124  */
125  public Set<String> getEntityPackages() {
126    return entityPackages;
127  }
128
129  /**
130  * Return true if transactional enhancement is turned off.
131  */
132  public boolean isTransactionalNone() {
133    return transactionalPackages.contains("none") && transactionalPackages.size() == 1;
134  }
135
136  /**
137  * Return true if we should use transient internal fields.
138  */
139  public boolean isTransientInternalFields() {
140    return transientInternalFields;
141  }
142
143  /**
144  * Return false if enhancement should skip checking for null many fields.
145  */
146  public boolean isCheckNullManyFields() {
147    return checkNullManyFields;
148  }
149
150  /**
151  * Return true if query bean enhancement is turned off.
152  */
153  public boolean isQueryBeanNone() {
154    return querybeanPackages.contains("none") && querybeanPackages.size() == 1;
155  }
156
157  /**
158  * Return the packages that should be enhanced for transactional.
159  * An empty set means all packages are scanned for transaction classes and methods.
160  */
161  public Set<String> getTransactionalPackages() {
162    return transactionalPackages;
163  }
164
165  /**
166  * Return the packages that should be enhanced for query bean use.
167  * An empty set means all packages are scanned for transaction classes and methods.
168  */
169  public Set<String> getQuerybeanPackages() {
170    return querybeanPackages;
171  }
172
173  /**
174  * Read all the specific manifest files and return the set of packages containing type query beans.
175  */
176  AgentManifest readManifests(ClassLoader classLoader, String path) throws IOException {
177    Enumeration<URL> resources = classLoader.getResources(path);
178    while (resources.hasMoreElements()) {
179      URL url = resources.nextElement();
180      try {
181        addResource(UrlHelper.openNoCache(url));
182      } catch (IOException e) {
183        System.err.println("Error reading manifest resources " + url);
184        e.printStackTrace();
185      }
186    }
187    return this;
188  }
189
190  /**
191  * Add given the manifest InputStream.
192  */
193  public void addResource(InputStream is) throws IOException {
194    try {
195      addManifest(new Manifest(is));
196    } finally {
197      try {
198        is.close();
199      } catch (IOException e) {
200        System.err.println("Error closing manifest resource");
201        e.printStackTrace();
202      }
203    }
204  }
205
206  void readProfilingMode(Attributes attributes) {
207
208    String debug = attributes.getValue("debug");
209    if (debug != null) {
210      debugLevel = Integer.parseInt(debug);
211    }
212
213    String locationMode = attributes.getValue("profile-location");
214    if (locationMode != null) {
215      enableProfileLocation = Boolean.parseBoolean(locationMode);
216    }
217
218    String queryLabelMode = attributes.getValue("query-labels");
219    if (queryLabelMode != null) {
220      enableQueryAutoLabel = Boolean.parseBoolean(queryLabelMode);
221    }
222
223    String mode = attributes.getValue("transaction-profiling");
224    if (mode != null) {
225      transactionProfilingMode = parseMode(mode);
226    }
227  }
228
229  private TxProfileMode parseMode(String mode) {
230    switch (mode.trim().toLowerCase()) {
231      case "enabled":
232      case "auto":
233      case "enable":
234        return TxProfileMode.ENABLED;
235      case "manual":
236        return TxProfileMode.MANUAL;
237      case "none":
238        return TxProfileMode.NONE;
239      default:
240        return TxProfileMode.NONE;
241    }
242  }
243
244  void readProfilingStart(Attributes attributes) {
245    String start = attributes.getValue("transaction-profiling-startvalue");
246    if (start != null) {
247      try {
248        transactionProfilingStart = Integer.parseInt(start);
249      } catch (NumberFormatException e) {
250        // ignore
251      }
252    }
253  }
254
255  private void addManifest(Manifest manifest) {
256    Attributes attributes = manifest.getMainAttributes();
257    readProfilingMode(attributes);
258    readProfilingStart(attributes);
259    readOptions(attributes);
260
261    add(entityPackages, attributes.getValue("packages"));
262    add(entityPackages, attributes.getValue("entity-packages"));
263    add(transactionalPackages, attributes.getValue("transactional-packages"));
264    add(querybeanPackages, attributes.getValue("querybean-packages"));
265  }
266
267  private void readOptions(Attributes attributes) {
268    transientInternalFields = bool("transient-internal-fields", transientInternalFields, attributes);
269    checkNullManyFields = bool("check-null-many-fields", checkNullManyFields, attributes);
270  }
271
272  private boolean bool(String key, boolean defaultValue, Attributes attributes) {
273    String val = attributes.getValue(key);
274    return val != null ? Boolean.parseBoolean(val) : defaultValue;
275  }
276
277  /**
278  * Collect each individual package splitting by delimiters.
279  */
280  private void add(Set<String> addTo, String packages) {
281    if (packages != null) {
282      String[] split = packages.split(",|;| ");
283      for (String aSplit : split) {
284        String pkg = aSplit.trim();
285        if (!pkg.isEmpty()) {
286          addTo.add(pkg);
287        }
288      }
289    }
290  }
291}