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