001package io.ebean.enhance.common; 002 003import io.ebean.enhance.Transformer; 004import io.ebean.enhance.entity.MessageOutput; 005 006import java.io.ByteArrayOutputStream; 007import java.io.PrintStream; 008import java.util.HashMap; 009import java.util.Set; 010import java.util.logging.Level; 011import java.util.logging.Logger; 012 013/** 014 * Used to hold meta data, arguments and log levels for the enhancement. 015 */ 016public class EnhanceContext { 017 018 private static final Logger logger = Logger.getLogger(EnhanceContext.class.getName()); 019 020 private final AgentManifest manifest; 021 022 private final IgnoreClassHelper ignoreClassHelper; 023 024 private final HashMap<String, String> agentArgsMap; 025 026 private final ClassMetaReader reader; 027 028 private final ClassBytesReader classBytesReader; 029 030 private MessageOutput logout; 031 032 private int logLevel; 033 034 private final HashMap<String, ClassMeta> map = new HashMap<>(); 035 036 private final FilterEntityTransactional filterEntityTransactional; 037 038 private final FilterQueryBean filterQueryBean; 039 040 private boolean throwOnError; 041 042 private final boolean enableProfileLocation; 043 private final int accPublic; 044 private final int accProtected; 045 private final int accPrivate; 046 private final int ebeanInternalVersion; 047 048 private SummaryInfo summaryInfo; 049 050 public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest) { 051 this(classBytesReader, agentArgs, manifest, new ClassMetaCache()); 052 } 053 054 /** 055 * Construct a context for enhancement. 056 */ 057 public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest, ClassMetaCache metaCache) { 058 this.manifest = manifest; 059 this.enableProfileLocation = manifest.isEnableProfileLocation(); 060 this.accPublic = manifest.accPublic(); 061 this.accProtected = manifest.accProtected(); 062 this.accPrivate = manifest.accPrivate(); 063 this.ebeanInternalVersion = manifest.getEbeanInternalVersion(); 064 this.agentArgsMap = ArgParser.parse(agentArgs); 065 this.filterEntityTransactional = new FilterEntityTransactional(manifest); 066 this.filterQueryBean = new FilterQueryBean(manifest); 067 068 this.ignoreClassHelper = new IgnoreClassHelper(); 069 this.logout = new SysoutMessageOutput(System.out); 070 this.classBytesReader = classBytesReader; 071 this.reader = new ClassMetaReader(this, metaCache); 072 073 if (manifest.getDebugLevel() > -1) { 074 logLevel = manifest.getDebugLevel(); 075 } 076 077 String debugValue = agentArgsMap.get("debug"); 078 if (debugValue != null) { 079 try { 080 logLevel = Integer.parseInt(debugValue); 081 } catch (NumberFormatException e) { 082 logger.log(Level.WARNING, "Agent debug argument [" + debugValue + "] is not an int?"); 083 } 084 } 085 086 if (getPropertyBoolean("printversion", false)) { 087 System.out.println("ebean agent version: " + Transformer.getVersion()); 088 } 089 } 090 091 public void withClassLoader(ClassLoader loader) { 092 if (manifest.readManifest(loader)) { 093 if (logLevel > 1) { 094 log(null, "loaded entity packages: " + manifest.getEntityPackages()); 095 } 096 } 097 } 098 099 public void setLogLevel(int logLevel) { 100 this.logLevel = logLevel; 101 } 102 103 /** 104 * Return the summary of the packages controlling enhancement. 105 */ 106 public String getPackagesSummary() { 107 return "packages entity:" + getEntityPackages() 108 + " transactional:" + getTransactionalPackages() 109 + " querybean:" + getQuerybeanPackages() 110 + " profileLocation:" + enableProfileLocation; 111 } 112 113 public Set<String> getEntityPackages() { 114 return manifest.getEntityPackages(); 115 } 116 117 public Set<String> getTransactionalPackages() { 118 return manifest.getTransactionalPackages(); 119 } 120 121 public Set<String> getQuerybeanPackages() { 122 return manifest.getQuerybeanPackages(); 123 } 124 125 public byte[] getClassBytes(String className, ClassLoader classLoader) { 126 return classBytesReader.getClassBytes(className, classLoader); 127 } 128 129 /** 130 * Return true if the owner class is a type query bean. 131 * <p> 132 * If true typically means the caller needs to change GETFIELD calls to instead invoke the generated 133 * 'property access' methods. 134 * </p> 135 */ 136 public boolean isQueryBean(String owner, ClassLoader classLoader) { 137 if (manifest.isDetectQueryBean(owner)) { 138 try { 139 final ClassMeta classMeta = reader.get(true, owner, classLoader); 140 if (classMeta == null) { 141 // For Gradle Kotlin KAPT the generate query bean bytecode 142 // isn't available to the classLoader. Just returning true. 143 return true; 144 } 145 return classMeta.isQueryBean(); 146 } catch (ClassNotFoundException e) { 147 throw new RuntimeException(e); 148 } 149 } 150 return false; 151 } 152 153 /** 154 * Return a value from the entity arguments using its key. 155 */ 156 private String getProperty(String key) { 157 return agentArgsMap.get(key.toLowerCase()); 158 } 159 160 private boolean getPropertyBoolean(String key, boolean defaultValue) { 161 String s = getProperty(key); 162 if (s == null) { 163 return defaultValue; 164 } else { 165 return s.trim().equalsIgnoreCase("true"); 166 } 167 } 168 169 /** 170 * Return true if profile location enhancement is on. 171 */ 172 public boolean isEnableProfileLocation() { 173 return enableProfileLocation; 174 } 175 176 /** 177 * Return true if this class should be scanned for transactional enhancement. 178 */ 179 public boolean detectEntityTransactionalEnhancement(String className) { 180 return filterEntityTransactional.detectEnhancement(className); 181 } 182 183 /** 184 * Return true if this class should be scanned for query bean enhancement. 185 */ 186 public boolean detectQueryBeanEnhancement(String className) { 187 return filterQueryBean.detectEnhancement(className); 188 } 189 190 /** 191 * Return true if this class should be ignored. That is JDK classes and 192 * known libraries JDBC drivers etc can be skipped. 193 */ 194 public boolean isIgnoreClass(String className) { 195 return ignoreClassHelper.isIgnoreClass(className); 196 } 197 198 /** 199 * Change the logout to something other than system out. 200 */ 201 public void setLogout(MessageOutput logout) { 202 this.logout = logout; 203 } 204 205 /** 206 * Create a new meta object for enhancing a class. 207 */ 208 public ClassMeta createClassMeta() { 209 return new ClassMeta(this, logLevel, logout); 210 } 211 212 /** 213 * Read the class meta data for a super class. 214 * <p> 215 * Typically used to read meta data for inheritance hierarchy. 216 * </p> 217 */ 218 public ClassMeta getSuperMeta(String superClassName, ClassLoader classLoader) { 219 220 try { 221 if (isIgnoreClass(superClassName)) { 222 return null; 223 } 224 return reader.get(false, superClassName, classLoader); 225 226 } catch (ClassNotFoundException e) { 227 throw new RuntimeException(e); 228 } 229 } 230 231 /** 232 * Read the class meta data for an interface. 233 * <p> 234 * Typically used to check the interface to see if it is transactional. 235 * </p> 236 */ 237 public ClassMeta getInterfaceMeta(String interfaceClassName, ClassLoader classLoader) { 238 239 try { 240 if (isIgnoreClass(interfaceClassName)) { 241 return null; 242 } 243 return reader.get(true, interfaceClassName, classLoader); 244 245 } catch (ClassNotFoundException e) { 246 throw new RuntimeException(e); 247 } 248 } 249 250 public void addClassMeta(ClassMeta meta) { 251 map.put(meta.getClassName(), meta); 252 } 253 254 public ClassMeta get(String className) { 255 return map.get(className); 256 } 257 258 /** 259 * Log some debug output. 260 */ 261 public void log(int level, String className, String msg) { 262 if (logLevel >= level) { 263 log(className, msg); 264 } 265 } 266 267 public void log(String className, String msg) { 268 if (className != null) { 269 msg = "cls: " + className + " msg: " + msg; 270 } 271 logout.println("ebean-enhance> " + msg); 272 } 273 274 public boolean isLog(int level) { 275 return logLevel >= level; 276 } 277 278 /** 279 * Log an error. 280 */ 281 public void log(Throwable e) { 282 e.printStackTrace( 283 new PrintStream(new ByteArrayOutputStream()) { 284 @Override 285 public void print(String message) { 286 logout.println(message); 287 } 288 289 @Override 290 public void println(String message) { 291 logout.println(message); 292 } 293 }); 294 } 295 296 297 /** 298 * Return the log level. 299 */ 300 public int getLogLevel() { 301 return logLevel; 302 } 303 304 /** 305 * Return true if internal ebean fields in entity classes should be transient. 306 */ 307 public boolean isTransientInternalFields() { 308 return manifest.isTransientInternalFields(); 309 } 310 311 /** 312 * Return true if we should add null checking on *ToMany fields. 313 * <p> 314 * On getting a many that is null Ebean will create an empty List, Set or Map. If it is a 315 * ManyToMany it will turn on Modify listening. 316 * </p> 317 */ 318 public boolean isCheckNullManyFields() { 319 return manifest.isCheckNullManyFields(); 320 } 321 322 /** 323 * Return true if transform should throw exception rather than log and return null. 324 */ 325 public boolean isThrowOnError() { 326 return throwOnError; 327 } 328 329 /** 330 * Set to true if you want transform to throw exceptions rather than return null. 331 */ 332 public void setThrowOnError(boolean throwOnError) { 333 this.throwOnError = throwOnError; 334 } 335 336 /** 337 * Turn on the summary collection of the enhancement. 338 */ 339 public void collectSummary() { 340 this.summaryInfo = new SummaryInfo(manifest.getLoadedResources()); 341 } 342 343 /** 344 * Add the transactional enhanced class to summary information. 345 */ 346 public void summaryTransactional(String className) { 347 if (summaryInfo != null) { 348 summaryInfo.addTransactional(className); 349 } 350 } 351 352 /** 353 * Add the entity enhanced class to summary information. 354 */ 355 public void summaryEntity(String className) { 356 if (summaryInfo != null) { 357 summaryInfo.addEntity(className); 358 } 359 } 360 361 /** 362 * Add the query bean enhanced class to summary information. 363 */ 364 public void summaryQueryBean(String className) { 365 if (summaryInfo != null) { 366 summaryInfo.addQueryBean(className); 367 } 368 } 369 370 /** 371 * Add the query bean caller enhanced class to summary information. 372 */ 373 public void summaryQueryBeanCaller(String className) { 374 if (summaryInfo != null) { 375 summaryInfo.addQueryBeanCaller(className); 376 } 377 } 378 379 /** 380 * Return the summary of the enhancement. 381 * <p> 382 * Note that <code>collectSummary()</code> must be called in order for summary 383 * information to be collected and returned here. 384 */ 385 public SummaryInfo getSummaryInfo() { 386 return summaryInfo.prepare(); 387 } 388 389 public int accPublic() { 390 return accPublic; 391 } 392 393 public int accProtected() { 394 return accProtected; 395 } 396 397 public int accPrivate() { 398 return accPrivate; 399 } 400 401 public boolean isToManyGetField() { 402 return ebeanInternalVersion > 128; 403 } 404}