001package io.ebean.enhance.common; 002 003import io.ebean.enhance.Transformer; 004import io.ebean.enhance.asm.MethodVisitor; 005import io.ebean.enhance.entity.MessageOutput; 006 007import java.io.ByteArrayOutputStream; 008import java.io.PrintStream; 009import java.util.HashMap; 010import java.util.Map; 011import java.util.Set; 012import java.util.logging.Level; 013import java.util.logging.Logger; 014 015import static io.ebean.enhance.asm.Opcodes.INVOKEINTERFACE; 016import static io.ebean.enhance.asm.Opcodes.INVOKEVIRTUAL; 017import static io.ebean.enhance.common.EnhanceConstants.C_INTERCEPT_I; 018import static io.ebean.enhance.common.EnhanceConstants.C_INTERCEPT_RW; 019 020/** 021 * Used to hold metadata, arguments and log levels for the enhancement. 022 */ 023public final class EnhanceContext { 024 025 private static final Logger logger = Logger.getLogger(EnhanceContext.class.getName()); 026 027 public enum ProfileLineNumberMode { 028 /** 029 * Line numbering on profile location when method + queryBean type is not unique 030 */ 031 AUTO, 032 /** 033 * No line numbering on profile locations 034 */ 035 NONE, 036 /** 037 * Line numbering on all profile locations 038 */ 039 ALL 040 } 041 042 private final AgentManifest manifest; 043 private final IgnoreClassHelper ignoreClassHelper; 044 private final Map<String, String> agentArgsMap; 045 private final ClassMetaReader reader; 046 private final ClassBytesReader classBytesReader; 047 private MessageOutput logout; 048 private int logLevel; 049 private final HashMap<String, ClassMeta> map = new HashMap<>(); 050 private final FilterEntityTransactional filterEntityTransactional; 051 private final FilterQueryBean filterQueryBean; 052 private final PackageFilter packageFilter; 053 private boolean throwOnError; 054 private final boolean enableProfileLocation; 055 private final boolean enableEntityFieldAccess; 056 private final ProfileLineNumberMode profileLineNumberMode; 057 private final int accPublic; 058 private final int accProtected; 059 private final int accPrivate; 060 private final int enhancementVersion; 061 private SummaryInfo summaryInfo; 062 063 public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest) { 064 this(classBytesReader, agentArgs, manifest, new ClassMetaCache()); 065 } 066 067 /** 068 * Construct a context for enhancement. 069 */ 070 public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest, ClassMetaCache metaCache) { 071 this.manifest = manifest; 072 this.enableProfileLocation = manifest.isEnableProfileLocation(); 073 this.enableEntityFieldAccess = manifest.isEnableEntityFieldAccess(); 074 this.profileLineNumberMode = manifest.profileLineMode(); 075 this.accPublic = manifest.accPublic(); 076 this.accProtected = manifest.accProtected(); 077 this.accPrivate = manifest.accPrivate(); 078 this.agentArgsMap = ArgParser.parse(agentArgs); 079 this.enhancementVersion = versionOf(manifest); 080 this.filterEntityTransactional = new FilterEntityTransactional(manifest); 081 this.filterQueryBean = new FilterQueryBean(manifest); 082 this.ignoreClassHelper = new IgnoreClassHelper(); 083 this.logout = new SysoutMessageOutput(System.out); 084 this.classBytesReader = classBytesReader; 085 this.reader = new ClassMetaReader(this, metaCache); 086 this.packageFilter = initPackageFilter(agentArgsMap.get("packages")); 087 088 if (manifest.debugLevel() > -1) { 089 logLevel = manifest.debugLevel(); 090 } 091 String debugValue = agentArgsMap.get("debug"); 092 if (debugValue != null) { 093 try { 094 logLevel = Integer.parseInt(debugValue); 095 } catch (NumberFormatException e) { 096 logger.log(Level.WARNING, "Agent debug argument [" + debugValue + "] is not an int?"); 097 } 098 } 099 if (logLevel > 0 || propertyBoolean("printversion", false)) { 100 System.out.println("ebean-agent version:" + Transformer.getVersion() + " enhancement:" + enhancementVersion + " resources:" + manifest.loadedResources()); 101 } 102 } 103 104 private PackageFilter initPackageFilter(String packages) { 105 return packages == null ? null : new PackageFilter(packages); 106 } 107 108 private int versionOf(AgentManifest manifest) { 109 String ver = agentArgsMap.get("version"); 110 if (ver != null) { 111 return Integer.parseInt(ver); 112 } 113 return manifest.enhancementVersion(); 114 } 115 116 public void withClassLoader(ClassLoader loader) { 117 if (manifest.readManifest(loader)) { 118 if (logLevel > 1) { 119 log(null, "loaded entity packages: " + manifest.entityPackages()); 120 } 121 } 122 } 123 124 public void setLogLevel(int logLevel) { 125 this.logLevel = logLevel; 126 } 127 128 @Deprecated 129 public String getPackagesSummary() { 130 return packagesSummary(); 131 } 132 133 /** 134 * Return the summary of the packages controlling enhancement. 135 */ 136 public String packagesSummary() { 137 return "packages entity:" + entityPackages() 138 + " transactional:" + transactionalPackages() 139 + " querybean:" + querybeanPackages() 140 + " profileLocation:" + enableProfileLocation 141 + " version:" + enhancementVersion; 142 } 143 144 public Set<String> entityPackages() { 145 return manifest.entityPackages(); 146 } 147 148 public Set<String> transactionalPackages() { 149 return manifest.transactionalPackages(); 150 } 151 152 public Set<String> querybeanPackages() { 153 return manifest.querybeanPackages(); 154 } 155 156 public byte[] classBytes(String className, ClassLoader classLoader) { 157 return classBytesReader.getClassBytes(className, classLoader); 158 } 159 160 public boolean isEntityBean(String owner) { 161 return manifest.isDetectEntityBean(owner); 162 } 163 164 /** 165 * Return true if the owner class is a type query bean. 166 * <p> 167 * If true typically means the caller needs to change GETFIELD calls to instead invoke the generated 168 * 'property access' methods. 169 */ 170 public boolean isQueryBean(String owner, ClassLoader classLoader) { 171 if (manifest.isDetectQueryBean(owner)) { 172 try { 173 final ClassMeta classMeta = reader.get(true, owner, classLoader); 174 if (classMeta == null) { 175 // For Gradle Kotlin KAPT the generate query bean bytecode 176 // isn't available to the classLoader. Just returning true. 177 return true; 178 } 179 return classMeta.isQueryBean(); 180 } catch (ClassNotFoundException e) { 181 throw new RuntimeException(e); 182 } 183 } 184 return false; 185 } 186 187 /** 188 * Return a value from the entity arguments using its key. 189 */ 190 private String property(String key) { 191 return agentArgsMap.get(key.toLowerCase()); 192 } 193 194 private boolean propertyBoolean(String key, boolean defaultValue) { 195 String s = property(key); 196 if (s == null) { 197 return defaultValue; 198 } else { 199 return s.trim().equalsIgnoreCase("true"); 200 } 201 } 202 203 public boolean isEnableEntityFieldAccess() { 204 return enableEntityFieldAccess; 205 } 206 207 /** 208 * Return true if profile location enhancement is on. 209 */ 210 public boolean isEnableProfileLocation() { 211 return enableProfileLocation; 212 } 213 214 /** 215 * Return true if this class should be scanned for transactional enhancement. 216 */ 217 public boolean detectEntityTransactionalEnhancement(String className) { 218 return filterEntityTransactional.detectEnhancement(className); 219 } 220 221 /** 222 * Return true if this class should be scanned for query bean enhancement. 223 */ 224 public boolean detectQueryBeanEnhancement(String className) { 225 return filterQueryBean.detectEnhancement(className); 226 } 227 228 /** 229 * Return true if this class should be ignored. That is JDK classes and 230 * known libraries JDBC drivers etc can be skipped. 231 */ 232 public boolean isIgnoreClass(String className) { 233 if (packageFilter != null && packageFilter.ignore(className)) { 234 return true; 235 } 236 return ignoreClassHelper.isIgnoreClass(className); 237 } 238 239 /** 240 * Change the logout to something other than system out. 241 */ 242 public void setLogout(MessageOutput logout) { 243 this.logout = logout; 244 } 245 246 /** 247 * Create a new meta object for enhancing a class. 248 */ 249 public ClassMeta createClassMeta() { 250 return new ClassMeta(this, logLevel, logout); 251 } 252 253 /** 254 * Read the class metadata for a super class. 255 * <p> 256 * Typically used to read meta data for inheritance hierarchy. 257 */ 258 public ClassMeta superMeta(String superClassName, ClassLoader classLoader) { 259 try { 260 if (isIgnoreClass(superClassName)) { 261 return null; 262 } 263 return reader.get(false, superClassName, classLoader); 264 } catch (ClassNotFoundException e) { 265 throw new RuntimeException(e); 266 } 267 } 268 269 /** 270 * Read the class metadata for an interface. 271 * <p> 272 * Typically used to check the interface to see if it is transactional. 273 */ 274 public ClassMeta interfaceMeta(String interfaceClassName, ClassLoader classLoader) { 275 try { 276 if (isIgnoreClass(interfaceClassName)) { 277 return null; 278 } 279 return reader.get(true, interfaceClassName, classLoader); 280 } catch (ClassNotFoundException e) { 281 throw new RuntimeException(e); 282 } 283 } 284 285 public void addClassMeta(ClassMeta meta) { 286 map.put(meta.className(), meta); 287 } 288 289 public ClassMeta get(String className) { 290 return map.get(className); 291 } 292 293 /** 294 * Log some debug output. 295 */ 296 public void log(int level, String className, String msg) { 297 if (logLevel >= level) { 298 log(className, msg); 299 } 300 } 301 302 public void log(String className, String msg) { 303 if (className != null) { 304 msg = "cls: " + className + " msg: " + msg; 305 } 306 logout.println("ebean-enhance> " + msg); 307 } 308 309 public boolean isLog(int level) { 310 return logLevel >= level; 311 } 312 313 /** 314 * Log an error. 315 */ 316 public void log(Throwable e) { 317 e.printStackTrace( 318 new PrintStream(new ByteArrayOutputStream()) { 319 @Override 320 public void print(String message) { 321 logout.println(message); 322 } 323 324 @Override 325 public void println(String message) { 326 logout.println(message); 327 } 328 }); 329 } 330 331 332 /** 333 * Return the log level. 334 */ 335 public int logLevel() { 336 return logLevel; 337 } 338 339 public boolean isTransientInit() { 340 return manifest.isTransientInit(); 341 } 342 343 public boolean isTransientInitThrowError() { 344 return manifest.isTransientInitThrowError(); 345 } 346 347 /** 348 * Return true if internal ebean fields in entity classes should be transient. 349 */ 350 public boolean isTransientInternalFields() { 351 return manifest.isTransientInternalFields(); 352 } 353 354 /** 355 * Return true if we should add null checking on *ToMany fields. 356 * <p> 357 * On getting a many that is null Ebean will create an empty List, Set or Map. If it is a 358 * ManyToMany it will turn on Modify listening. 359 */ 360 public boolean isCheckNullManyFields() { 361 return manifest.isCheckNullManyFields(); 362 } 363 364 public boolean isAllowNullableDbArray() { 365 return manifest.isAllowNullableDbArray(); 366 } 367 368 /** 369 * Return true if transform should throw exception rather than log and return null. 370 */ 371 public boolean isThrowOnError() { 372 return throwOnError; 373 } 374 375 /** 376 * Set to true if you want transform to throw exceptions rather than return null. 377 */ 378 public void setThrowOnError(boolean throwOnError) { 379 this.throwOnError = throwOnError; 380 } 381 382 /** 383 * Turn on the summary collection of the enhancement. 384 */ 385 public void collectSummary() { 386 this.summaryInfo = new SummaryInfo(manifest.loadedResources()); 387 } 388 389 /** 390 * Add the transactional enhanced class to summary information. 391 */ 392 public void summaryTransactional(String className) { 393 if (summaryInfo != null) { 394 summaryInfo.addTransactional(className); 395 } 396 } 397 398 /** 399 * Add the entity enhanced class to summary information. 400 */ 401 public void summaryEntity(String className) { 402 if (summaryInfo != null) { 403 summaryInfo.addEntity(className); 404 } 405 } 406 407 /** 408 * Add the query bean enhanced class to summary information. 409 */ 410 public void summaryQueryBean(String className) { 411 if (summaryInfo != null) { 412 summaryInfo.addQueryBean(className); 413 } 414 } 415 416 /** 417 * Add the query bean caller enhanced class to summary information. 418 */ 419 public void summaryQueryBeanCaller(String className) { 420 if (summaryInfo != null) { 421 summaryInfo.addQueryBeanCaller(className); 422 } 423 } 424 425 /** 426 * Add the enhanced class with field access replacement to summary information. 427 */ 428 public void summaryFieldAccessUser(String className) { 429 if (summaryInfo != null) { 430 summaryInfo.addFieldAccessUser(className); 431 } 432 } 433 434 @Deprecated 435 public SummaryInfo getSummaryInfo() { 436 return summaryInfo(); 437 } 438 439 /** 440 * Return the summary of the enhancement. 441 * <p> 442 * Note that <code>collectSummary()</code> must be called in order for summary 443 * information to be collected and returned here. 444 */ 445 public SummaryInfo summaryInfo() { 446 return summaryInfo.prepare(); 447 } 448 449 public int accPublic() { 450 return accPublic; 451 } 452 453 public int accProtected() { 454 return accProtected; 455 } 456 457 public int accPrivate() { 458 return accPrivate; 459 } 460 461 public boolean isToManyGetField() { 462 return enhancementVersion > 128; 463 } 464 465 public boolean isEnhancedToString() { 466 return enhancementVersion > 132; 467 } 468 469 public String interceptNew() { 470 return enhancementVersion >= 140 ? C_INTERCEPT_RW : C_INTERCEPT_I; 471 } 472 473 public void visitMethodInsnIntercept(MethodVisitor mv, String name, String desc) { 474 mv.visitMethodInsn(interceptInvoke(), C_INTERCEPT_I, name, desc, interceptIface()); 475 } 476 477 private int interceptInvoke() { 478 return enhancementVersion >= 140 ? INVOKEINTERFACE : INVOKEVIRTUAL; 479 } 480 481 private boolean interceptIface() { 482 return enhancementVersion >= 140; 483 } 484 485 public boolean interceptAddReadOnly() { 486 return enhancementVersion >= 141; 487 } 488 489 public boolean supportsProfileWithLine() { 490 return enhancementVersion >= 143; // Ebean 13.13.1 onwards 491 } 492 493 public boolean improvedQueryBeans() { 494 return enhancementVersion >= 145; 495 } 496 497 public boolean fluidQueryBuilders() { 498 return enhancementVersion >= 148; 499 } 500 501 public ProfileLineNumberMode profileLineMode() { 502 return profileLineNumberMode; 503 } 504}