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