All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.ebean.enhance.common.AgentManifest Maven / Gradle / Ivy

package io.ebean.enhance.common;

import io.ebean.enhance.querybean.DetectQueryBean;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import static io.ebean.enhance.asm.Opcodes.*;

/**
 * Reads all the META-INF/ebean.mf and META-INF/ebean-generated-info.mf resources with the locations
 * of all the entity beans (and hence locations of query beans).
 */
public final class AgentManifest {

  private final Set classLoaderIdentities = new HashSet<>();
  private final List loadedResources = new ArrayList<>();
  private final Set entityPackages = new HashSet<>();
  private final Set transactionalPackages = new HashSet<>();
  private final Set querybeanPackages = new HashSet<>();
  private final DetectQueryBean detectQueryBean;
  private int debugLevel = -1;
  private boolean allowNullableDbArray;
  private boolean transientInternalFields;
  private boolean transientInit;
  private boolean transientInitThrowError;
  private boolean checkNullManyFields = true;
  private boolean enableProfileLocation = true;
  private boolean enableEntityFieldAccess;
  /**
   * Default the mode to NONE for consistency with existing behavior. Long term preference is AUTO.
   */
  private EnhanceContext.ProfileLineNumberMode profileLineNumberMode = EnhanceContext.ProfileLineNumberMode.NONE;
  private boolean synthetic = true;
  private int enhancementVersion;

  public AgentManifest(ClassLoader classLoader) {
    this.detectQueryBean = new DetectQueryBean();
    readManifest(classLoader);
  }

  public AgentManifest() {
    this.detectQueryBean = new DetectQueryBean();
  }

  /**
   * Return true if more entity packages were loaded.
   */
  public boolean readManifest(ClassLoader classLoader) {
    if (classLoader == null) {
      return false;
    }
    final int loaderIdentity = System.identityHashCode(classLoader);
    if (classLoaderIdentities.add(loaderIdentity)) {
      try {
        int beforeSize = entityPackages.size();
        readEbeanVersion(classLoader, "META-INF/ebean-version.mf");
        readManifests(classLoader, "META-INF/ebean-generated-info.mf");
        readManifests(classLoader, "META-INF/ebean.mf");
        readManifests(classLoader, "ebean.mf");
        int afterSize = entityPackages.size();
        if (afterSize > beforeSize) {
          detectQueryBean.addAll(entityPackages);
          return true;
        }
      } catch (IOException e) {
        // log to standard error and return empty
        System.err.println("Agent: error reading ebean manifest resources");
        e.printStackTrace();
      }
    }
    return false;
  }

  @Override
  public String toString() {
    return "entityPackages:" + entityPackages + " querybeanPackages:" + querybeanPackages
      + " transactionalPackages:" + transactionalPackages;
  }

  public boolean isDetectEntityBean(String owner) {
    return detectQueryBean.isEntityBean(owner);
  }

  public boolean isDetectQueryBean(String owner) {
    return detectQueryBean.isQueryBean(owner);
  }

  public int enhancementVersion() {
    return enhancementVersion;
  }

  /**
   * Return true if enhancement of profileLocations should be added.
   */
  public boolean isEnableProfileLocation() {
    return enableProfileLocation;
  }

  public boolean isEnableEntityFieldAccess() {
    return enableEntityFieldAccess;
  }

  public EnhanceContext.ProfileLineNumberMode profileLineMode() {
    return profileLineNumberMode;
  }

  /**
   * Return the debug level read from ebean.mf
   */
  public int debugLevel() {
    return debugLevel;
  }

  /**
   * Return the paths that manifests were loaded from.
   */
  public List loadedResources() {
    return loadedResources;
  }

  /**
   * Return the parsed set of packages that type query beans are in.
   */
  public Set entityPackages() {
    return entityPackages;
  }

  /**
   * Return true if transactional enhancement is turned off.
   */
  public boolean isTransactionalNone() {
    return transactionalPackages.contains("none") && transactionalPackages.size() == 1;
  }

  /**
   * Return true if enhancement should add initialisation of transient fields when adding a default constructor.
   */
  public boolean isTransientInit() {
    return transientInit;
  }

  /**
   * Return true if an error should be thrown when adding a default constructor and transient fields can't be initialised.
   */
  public boolean isTransientInitThrowError() {
    return transientInitThrowError;
  }

  /**
   * Return true if we should use transient internal fields.
   */
  public boolean isTransientInternalFields() {
    return transientInternalFields;
  }

  /**
   * Return false if enhancement should skip checking for null many fields.
   */
  public boolean isCheckNullManyFields() {
    return checkNullManyFields;
  }

  public boolean isAllowNullableDbArray() {
    return allowNullableDbArray;
  }

  public int accPublic() {
    return synthetic ? (ACC_PUBLIC + ACC_SYNTHETIC) : ACC_PUBLIC;
  }

  public int accProtected() {
    return synthetic ? (ACC_PROTECTED + ACC_SYNTHETIC) : ACC_PROTECTED;
  }

  public int accPrivate() {
    return synthetic ? (ACC_PRIVATE + ACC_SYNTHETIC) : ACC_PRIVATE;
  }

  /**
   * Return true if query bean enhancement is turned off.
   */
  public boolean isQueryBeanNone() {
    return querybeanPackages.contains("none") && querybeanPackages.size() == 1;
  }

  /**
   * Return the packages that should be enhanced for transactional.
   * An empty set means all packages are scanned for transaction classes and methods.
   */
  public Set transactionalPackages() {
    return transactionalPackages;
  }

  /**
   * Return the packages that should be enhanced for query bean use.
   * An empty set means all packages are scanned for transaction classes and methods.
   */
  public Set querybeanPackages() {
    return querybeanPackages;
  }

  protected void readEbeanVersion(ClassLoader classLoader, String path) throws IOException {
    Enumeration resources = classLoader.getResources(path);
    while (resources.hasMoreElements()) {
      URL url = resources.nextElement();
      try {
        final Manifest manifest = manifest(UrlHelper.openNoCache(url));
        final String value = manifest.getMainAttributes().getValue("ebean-version");
        if (value != null) {
          enhancementVersion = Integer.parseInt(value.trim());
          if (enhancementVersion > 141) {
            // default these to true for ebean version 13.12.0 or higher
            allowNullableDbArray = true;
            transientInit = true;
            transientInitThrowError = true;
          }
        }
        loadedResources.add(path);
      } catch (Exception e) {
        System.err.println("Error reading manifest resources " + url);
        e.printStackTrace();
      }
    }
  }

  /**
   * Read all the specific manifest files and return the set of packages containing type query beans.
   */
  void readManifests(ClassLoader classLoader, String path) throws IOException {
    Enumeration resources = classLoader.getResources(path);
    while (resources.hasMoreElements()) {
      URL url = resources.nextElement();
      try {
        addResource(UrlHelper.openNoCache(url));
        loadedResources.add(path);
      } catch (IOException e) {
        System.err.println("Error reading manifest resources " + url);
        e.printStackTrace();
      }
    }
  }

  /**
   * Add given the manifest InputStream.
   */
  private void addResource(InputStream is) throws IOException {
    addManifest(manifest(is));
  }

  private Manifest manifest(InputStream is) throws IOException {
    try {
      return new Manifest(is);
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        System.err.println("Error closing manifest resource");
        e.printStackTrace();
      }
    }
  }

  private void readProfilingMode(Attributes attributes) {
    String debug = attributes.getValue("debug");
    if (debug != null) {
      debugLevel = Integer.parseInt(debug);
    }
    String locationMode = attributes.getValue("profile-location");
    if (locationMode != null) {
      enableProfileLocation = Boolean.parseBoolean(locationMode);
    }
    String profileWithLine = attributes.getValue("profile-line-number-mode");
    if (profileWithLine != null) {
      try {
        profileLineNumberMode = EnhanceContext.ProfileLineNumberMode.valueOf(profileWithLine.toUpperCase().trim());
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    String fieldAccessMode = attributes.getValue("entity-field-access");
    if (fieldAccessMode != null) {
      enableEntityFieldAccess = Boolean.parseBoolean(fieldAccessMode);
    }
    String syntheticOption = attributes.getValue("synthetic");
    if (syntheticOption != null) {
      synthetic = Boolean.parseBoolean(syntheticOption);
    }
  }

  private void addManifest(Manifest manifest) {
    Attributes attributes = manifest.getMainAttributes();
    readProfilingMode(attributes);
    readOptions(attributes);

    add(entityPackages, attributes.getValue("packages"));
    add(entityPackages, attributes.getValue("entity-packages"));
    add(transactionalPackages, attributes.getValue("transactional-packages"));
    add(querybeanPackages, attributes.getValue("querybean-packages"));

    final String topPackages = attributes.getValue("top-packages");
    if (topPackages != null) {
      add(transactionalPackages, topPackages);
      add(querybeanPackages, topPackages);
    }
  }

  private void readOptions(Attributes attributes) {
    transientInit = bool("transient-init", transientInit, attributes);
    transientInitThrowError = bool("transient-init-error", transientInit, attributes);
    transientInternalFields = bool("transient-internal-fields", transientInternalFields, attributes);
    checkNullManyFields = bool("check-null-many-fields", checkNullManyFields, attributes);
    allowNullableDbArray = bool("allow-nullable-dbarray", allowNullableDbArray, attributes);
  }

  private boolean bool(String key, boolean defaultValue, Attributes attributes) {
    String val = attributes.getValue(key);
    return val != null ? Boolean.parseBoolean(val) : defaultValue;
  }

  /**
   * Collect each individual package splitting by delimiters.
   */
  private void add(Set addTo, String packages) {
    if (packages != null) {
      String[] split = packages.split("[,; ]");
      for (String aSplit : split) {
        String pkg = aSplit.trim();
        if (!pkg.isEmpty()) {
          addTo.add(pkg);
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy