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