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  private 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  private 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  private 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      default:
238        return TxProfileMode.NONE;
239    }
240  }
241
242  private void readProfilingStart(Attributes attributes) {
243    String start = attributes.getValue("transaction-profiling-startvalue");
244    if (start != null) {
245      try {
246        transactionProfilingStart = Integer.parseInt(start);
247      } catch (NumberFormatException e) {
248        // ignore
249      }
250    }
251  }
252
253  private void addManifest(Manifest manifest) {
254    Attributes attributes = manifest.getMainAttributes();
255    readProfilingMode(attributes);
256    readProfilingStart(attributes);
257    readOptions(attributes);
258
259    add(entityPackages, attributes.getValue("packages"));
260    add(entityPackages, attributes.getValue("entity-packages"));
261    add(transactionalPackages, attributes.getValue("transactional-packages"));
262    add(querybeanPackages, attributes.getValue("querybean-packages"));
263
264    final String topPackages = attributes.getValue("top-packages");
265    if (topPackages != null) {
266      add(transactionalPackages, topPackages);
267      add(querybeanPackages, topPackages);
268    }
269  }
270
271  private void readOptions(Attributes attributes) {
272    transientInternalFields = bool("transient-internal-fields", transientInternalFields, attributes);
273    checkNullManyFields = bool("check-null-many-fields", checkNullManyFields, attributes);
274  }
275
276  private boolean bool(String key, boolean defaultValue, Attributes attributes) {
277    String val = attributes.getValue(key);
278    return val != null ? Boolean.parseBoolean(val) : defaultValue;
279  }
280
281  /**
282  * Collect each individual package splitting by delimiters.
283  */
284  private void add(Set<String> addTo, String packages) {
285    if (packages != null) {
286      String[] split = packages.split(",|;| ");
287      for (String aSplit : split) {
288        String pkg = aSplit.trim();
289        if (!pkg.isEmpty()) {
290          addTo.add(pkg);
291        }
292      }
293    }
294  }
295}