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