001package io.ebean.enhance.common; 002 003import java.io.IOException; 004import java.io.InputStream; 005import java.net.URL; 006import java.util.Enumeration; 007import java.util.HashSet; 008import java.util.Set; 009import java.util.jar.Attributes; 010import java.util.jar.Manifest; 011 012/** 013 * Reads all the META-INF/ebean.mf and META-INF/ebean-typequery.mf resources with the locations 014 * of all the entity beans (and hence locations of query beans). 015 */ 016public class AgentManifest { 017 018 enum TxProfileMode { 019 NONE, 020 ENABLED, 021 MANUAL 022 } 023 024 private final Set<String> entityPackages = new HashSet<>(); 025 026 private final Set<String> transactionalPackages = new HashSet<>(); 027 028 private final Set<String> querybeanPackages = new HashSet<>(); 029 030 private TxProfileMode transactionProfilingMode = TxProfileMode.NONE; 031 032 /** 033 * Start profileId when automatically assigned by enhancement. 034 */ 035 private int transactionProfilingStart = 1000; 036 037 private boolean transientInternalFields; 038 039 private boolean checkNullManyFields = true; 040 041 public static AgentManifest read(ClassLoader classLoader, Set<String> initialPackages) { 042 043 try { 044 return new AgentManifest(initialPackages) 045 .readManifests(classLoader, "META-INF/ebean-typequery.mf") 046 .readManifests(classLoader, "META-INF/ebean.mf") 047 .readManifests(classLoader, "ebean.mf"); 048 049 } catch (IOException e) { 050 // log to standard error and return empty 051 System.err.println("Agent: error reading ebean manifest resources"); 052 e.printStackTrace(); 053 return new AgentManifest(); 054 } 055 } 056 057 /** 058 * Construct with some packages defined externally. 059 */ 060 public AgentManifest(Set<String> initialPackages) { 061 if (initialPackages != null) { 062 entityPackages.addAll(initialPackages); 063 } 064 } 065 066 /** 067 * Construct with no initial packages (to use with addRaw()). 068 */ 069 public AgentManifest() { 070 } 071 072 public String toString() { 073 return "entityPackages:" + entityPackages + " querybeanPackages:" + querybeanPackages 074 + " transactionalPackages:" + transactionalPackages + " profilingMode:" + transactionProfilingMode; 075 } 076 077 /** 078 * Return the initial starting profileId when automatically assigned. 079 */ 080 int transactionProfilingStart() { 081 switch (transactionProfilingMode) { 082 case NONE: 083 return -1; 084 case MANUAL: 085 return 0; 086 case ENABLED: 087 return transactionProfilingStart; 088 default: { 089 return transactionProfilingStart; 090 } 091 } 092 } 093 094 /** 095 * Return the parsed set of packages that type query beans are in. 096 */ 097 public Set<String> getEntityPackages() { 098 return entityPackages; 099 } 100 101 /** 102 * Return true if transactional enhancement is turned off. 103 */ 104 public boolean isTransactionalNone() { 105 return transactionalPackages.contains("none") && transactionalPackages.size() == 1; 106 } 107 108 /** 109 * Return true if we should use transient internal fields. 110 */ 111 public boolean isTransientInternalFields() { 112 return transientInternalFields; 113 } 114 115 /** 116 * Return false if enhancement should skip checking for null many fields. 117 */ 118 public boolean isCheckNullManyFields() { 119 return checkNullManyFields; 120 } 121 122 /** 123 * Return true if query bean enhancement is turned off. 124 */ 125 public boolean isQueryBeanNone() { 126 return querybeanPackages.contains("none") && querybeanPackages.size() == 1; 127 } 128 129 /** 130 * Return the packages that should be enhanced for transactional. 131 * An empty set means all packages are scanned for transaction classes and methods. 132 */ 133 public Set<String> getTransactionalPackages() { 134 return transactionalPackages; 135 } 136 137 /** 138 * Return the packages that should be enhanced for query bean use. 139 * An empty set means all packages are scanned for transaction classes and methods. 140 */ 141 public Set<String> getQuerybeanPackages() { 142 return querybeanPackages; 143 } 144 145 /** 146 * Read all the specific manifest files and return the set of packages containing type query beans. 147 */ 148 AgentManifest readManifests(ClassLoader classLoader, String path) throws IOException { 149 Enumeration<URL> resources = classLoader.getResources(path); 150 while (resources.hasMoreElements()) { 151 URL url = resources.nextElement(); 152 try { 153 addResource(url.openStream()); 154 } catch (IOException e) { 155 System.err.println("Error reading manifest resources " + url); 156 e.printStackTrace(); 157 } 158 } 159 return this; 160 } 161 162 /** 163 * Add given the manifest InputStream. 164 */ 165 public void addResource(InputStream is) throws IOException { 166 try { 167 addManifest(new Manifest(is)); 168 } finally { 169 try { 170 is.close(); 171 } catch (IOException e) { 172 System.err.println("Error closing manifest resource"); 173 e.printStackTrace(); 174 } 175 } 176 } 177 178 void readProfilingMode(Attributes attributes) { 179 String mode = attributes.getValue("transaction-profiling"); 180 if (mode != null) { 181 transactionProfilingMode = parseMode(mode); 182 } 183 } 184 185 private TxProfileMode parseMode(String mode) { 186 switch (mode.trim().toLowerCase()) { 187 case "enabled": 188 case "auto": 189 case "enable": 190 return TxProfileMode.ENABLED; 191 case "manual": 192 return TxProfileMode.MANUAL; 193 case "none": 194 return TxProfileMode.NONE; 195 default: 196 return TxProfileMode.NONE; 197 } 198 } 199 200 void readProfilingStart(Attributes attributes) { 201 String start = attributes.getValue("transaction-profiling-startvalue"); 202 if (start != null) { 203 try { 204 transactionProfilingStart = Integer.parseInt(start); 205 } catch (NumberFormatException e) { 206 // ignore 207 } 208 } 209 } 210 211 private void addManifest(Manifest manifest) { 212 Attributes attributes = manifest.getMainAttributes(); 213 readProfilingMode(attributes); 214 readProfilingStart(attributes); 215 readOptions(attributes); 216 217 add(entityPackages, attributes.getValue("packages")); 218 add(entityPackages, attributes.getValue("entity-packages")); 219 add(transactionalPackages, attributes.getValue("transactional-packages")); 220 add(querybeanPackages, attributes.getValue("querybean-packages")); 221 } 222 223 private void readOptions(Attributes attributes) { 224 transientInternalFields = bool("transient-internal-fields", transientInternalFields, attributes); 225 checkNullManyFields = bool("check-null-many-fields", checkNullManyFields, attributes); 226 } 227 228 private boolean bool(String key, boolean defaultValue, Attributes attributes) { 229 String val = attributes.getValue(key); 230 return val != null ? Boolean.parseBoolean(val) : defaultValue; 231 } 232 233 /** 234 * Collect each individual package splitting by delimiters. 235 */ 236 private void add(Set<String> addTo, String packages) { 237 if (packages != null) { 238 String[] split = packages.split(",|;| "); 239 for (String aSplit : split) { 240 String pkg = aSplit.trim(); 241 if (!pkg.isEmpty()) { 242 addTo.add(pkg); 243 } 244 } 245 } 246 } 247}