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}