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 boolean transientInternalFields;
038
039  private boolean checkNullManyFields = true;
040
041  public static AgentManifest read(ClassLoader classLoader, Set<String> initialPackages) {
042
043    try {
044      return new AgentManifest(initialPackages)
045          .readManifests(classLoader, "META-INF/ebean-typequery.mf")
046          .readManifests(classLoader, "META-INF/ebean.mf")
047          .readManifests(classLoader, "ebean.mf");
048
049    } catch (IOException e) {
050      // log to standard error and return empty
051      System.err.println("Agent: error reading ebean manifest resources");
052      e.printStackTrace();
053      return new AgentManifest();
054    }
055  }
056
057  /**
058   * Construct with some packages defined externally.
059   */
060  public AgentManifest(Set<String> initialPackages) {
061    if (initialPackages != null) {
062      entityPackages.addAll(initialPackages);
063    }
064  }
065
066  /**
067   * Construct with no initial packages (to use with addRaw()).
068   */
069  public AgentManifest() {
070  }
071
072  public String toString() {
073    return "entityPackages:" + entityPackages + " querybeanPackages:" + querybeanPackages
074      + " transactionalPackages:" + transactionalPackages + " profilingMode:" + transactionProfilingMode;
075  }
076
077  /**
078   * Return the initial starting profileId when automatically assigned.
079   */
080  int transactionProfilingStart() {
081    switch (transactionProfilingMode) {
082      case NONE:
083        return -1;
084      case MANUAL:
085        return 0;
086      case ENABLED:
087        return transactionProfilingStart;
088      default: {
089        return transactionProfilingStart;
090      }
091    }
092  }
093
094  /**
095   * Return the parsed set of packages that type query beans are in.
096   */
097  public Set<String> getEntityPackages() {
098    return entityPackages;
099  }
100
101  /**
102   * Return true if transactional enhancement is turned off.
103   */
104  public boolean isTransactionalNone() {
105    return transactionalPackages.contains("none") && transactionalPackages.size() == 1;
106  }
107
108  /**
109   * Return true if we should use transient internal fields.
110   */
111  public boolean isTransientInternalFields() {
112    return transientInternalFields;
113  }
114
115  /**
116   * Return false if enhancement should skip checking for null many fields.
117   */
118  public boolean isCheckNullManyFields() {
119    return checkNullManyFields;
120  }
121
122  /**
123   * Return true if query bean enhancement is turned off.
124   */
125  public boolean isQueryBeanNone() {
126    return querybeanPackages.contains("none") && querybeanPackages.size() == 1;
127  }
128
129  /**
130   * Return the packages that should be enhanced for transactional.
131   * An empty set means all packages are scanned for transaction classes and methods.
132   */
133  public Set<String> getTransactionalPackages() {
134    return transactionalPackages;
135  }
136
137  /**
138   * Return the packages that should be enhanced for query bean use.
139   * An empty set means all packages are scanned for transaction classes and methods.
140   */
141  public Set<String> getQuerybeanPackages() {
142    return querybeanPackages;
143  }
144
145  /**
146   * Read all the specific manifest files and return the set of packages containing type query beans.
147   */
148  AgentManifest readManifests(ClassLoader classLoader, String path) throws IOException {
149    Enumeration<URL> resources = classLoader.getResources(path);
150    while (resources.hasMoreElements()) {
151      URL url = resources.nextElement();
152      try {
153        addResource(url.openStream());
154      } catch (IOException e) {
155        System.err.println("Error reading manifest resources " + url);
156        e.printStackTrace();
157      }
158    }
159    return this;
160  }
161
162  /**
163   * Add given the manifest InputStream.
164   */
165  public void addResource(InputStream is) throws IOException {
166    try {
167      addManifest(new Manifest(is));
168    } finally {
169      try {
170        is.close();
171      } catch (IOException e) {
172        System.err.println("Error closing manifest resource");
173        e.printStackTrace();
174      }
175    }
176  }
177
178  void readProfilingMode(Attributes attributes) {
179    String mode = attributes.getValue("transaction-profiling");
180    if (mode != null) {
181      transactionProfilingMode = parseMode(mode);
182    }
183  }
184
185  private TxProfileMode parseMode(String mode) {
186    switch (mode.trim().toLowerCase()) {
187      case "enabled":
188      case "auto":
189      case "enable":
190        return TxProfileMode.ENABLED;
191      case "manual":
192        return TxProfileMode.MANUAL;
193      case "none":
194        return TxProfileMode.NONE;
195      default:
196        return TxProfileMode.NONE;
197    }
198  }
199
200  void readProfilingStart(Attributes attributes) {
201    String start = attributes.getValue("transaction-profiling-startvalue");
202    if (start != null) {
203      try {
204        transactionProfilingStart = Integer.parseInt(start);
205      } catch (NumberFormatException e) {
206        // ignore
207      }
208    }
209  }
210
211  private void addManifest(Manifest manifest) {
212    Attributes attributes = manifest.getMainAttributes();
213    readProfilingMode(attributes);
214    readProfilingStart(attributes);
215    readOptions(attributes);
216
217    add(entityPackages, attributes.getValue("packages"));
218    add(entityPackages, attributes.getValue("entity-packages"));
219    add(transactionalPackages, attributes.getValue("transactional-packages"));
220    add(querybeanPackages, attributes.getValue("querybean-packages"));
221  }
222
223  private void readOptions(Attributes attributes) {
224    transientInternalFields = bool("transient-internal-fields", transientInternalFields, attributes);
225    checkNullManyFields = bool("check-null-many-fields", checkNullManyFields, attributes);
226  }
227
228  private boolean bool(String key, boolean defaultValue, Attributes attributes) {
229    String val = attributes.getValue(key);
230    return val != null ? Boolean.parseBoolean(val) : defaultValue;
231  }
232
233  /**
234   * Collect each individual package splitting by delimiters.
235   */
236  private void add(Set<String> addTo, String packages) {
237    if (packages != null) {
238      String[] split = packages.split(",|;| ");
239      for (String aSplit : split) {
240        String pkg = aSplit.trim();
241        if (!pkg.isEmpty()) {
242          addTo.add(pkg);
243        }
244      }
245    }
246  }
247}