001package io.ebean.enhance.common; 002 003import io.ebean.enhance.Transformer; 004import io.ebean.enhance.entity.MessageOutput; 005import io.ebean.enhance.querybean.DetectQueryBean; 006import io.ebean.enhance.querybean.Distill; 007import io.ebean.enhance.transactional.TransactionalMethodKey; 008 009import java.io.ByteArrayOutputStream; 010import java.io.PrintStream; 011import java.util.ArrayList; 012import java.util.HashMap; 013import java.util.List; 014import java.util.logging.Level; 015import java.util.logging.Logger; 016 017/** 018 * Used to hold meta data, arguments and log levels for the enhancement. 019 */ 020public class EnhanceContext { 021 022 private static final Logger logger = Logger.getLogger(EnhanceContext.class.getName()); 023 024 private final IgnoreClassHelper ignoreClassHelper; 025 026 private final HashMap<String, String> agentArgsMap; 027 028 private final boolean transientInternalFields; 029 030 private final boolean checkNullManyFields; 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 DetectQueryBean detectQueryBean; 043 044 private final FilterEntityTransactional filterEntityTransactional; 045 046 private final FilterQueryBean filterQueryBean; 047 048 /** 049 * Current profileId when automatically assigned. 050 */ 051 private int autoProfileId; 052 053 private boolean throwOnError; 054 055 private final boolean enableProfileLocation; 056 057 private final boolean enableQueryAutoLabel; 058 059 /** 060 * Mapping of profileId to transactional method descriptions (for decoding profiling). 061 */ 062 private final List<TransactionalMethodKey> profilingKeys = new ArrayList<>(); 063 064 public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest) { 065 this(classBytesReader, agentArgs, manifest, new ClassMetaCache()); 066 } 067 068 /** 069 * Construct a context for enhancement. 070 */ 071 public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest, ClassMetaCache metaCache) { 072 073 this.autoProfileId = manifest.transactionProfilingStart(); 074 this.enableProfileLocation = manifest.isEnableProfileLocation(); 075 this.enableQueryAutoLabel = manifest.isEnableQueryAutoLabel(); 076 077 this.agentArgsMap = ArgParser.parse(agentArgs); 078 this.filterEntityTransactional = new FilterEntityTransactional(manifest); 079 this.filterQueryBean = new FilterQueryBean(manifest); 080 081 this.detectQueryBean = Distill.convert(manifest.getEntityPackages()); 082 if (detectQueryBean.isEmpty()) { 083 logger.log(Level.FINE, "No ebean.mf detected"); 084 } 085 086 this.ignoreClassHelper = new IgnoreClassHelper(); 087 this.logout = new SysoutMessageOutput(System.out); 088 this.classBytesReader = classBytesReader; 089 this.reader = new ClassMetaReader(this, metaCache); 090 091 if (manifest.getDebugLevel() > -1) { 092 logLevel = manifest.getDebugLevel(); 093 } 094 095 String debugValue = agentArgsMap.get("debug"); 096 if (debugValue != null) { 097 try { 098 logLevel = Integer.parseInt(debugValue); 099 } catch (NumberFormatException e) { 100 logger.log(Level.WARNING, "Agent debug argument [" + debugValue + "] is not an int?"); 101 } 102 } 103 104 this.transientInternalFields = getPropertyBoolean("transientInternalFields", manifest.isTransientInternalFields()); 105 this.checkNullManyFields = getPropertyBoolean("checkNullManyFields", manifest.isCheckNullManyFields()); 106 if (getPropertyBoolean("printversion", false)) { 107 System.out.println("ebean agent version: " + Transformer.getVersion()); 108 } 109 } 110 111 public byte[] getClassBytes(String className, ClassLoader classLoader) { 112 return classBytesReader.getClassBytes(className, classLoader); 113 } 114 115 /** 116 * Return true if the owner class is a type query bean. 117 * <p> 118 * If true typically means the caller needs to change GETFIELD calls to instead invoke the generated 119 * 'property access' methods. 120 * </p> 121 */ 122 public boolean isQueryBean(String owner, ClassLoader classLoader) { 123 if (detectQueryBean.isQueryBean(owner)) { 124 try { 125 final ClassMeta classMeta = reader.get(true, owner, classLoader); 126 return classMeta.isQueryBean(); 127 } catch (ClassNotFoundException e) { 128 throw new RuntimeException(e); 129 } 130 } 131 return false; 132 } 133 134 /** 135 * Return a value from the entity arguments using its key. 136 */ 137 private String getProperty(String key) { 138 return agentArgsMap.get(key.toLowerCase()); 139 } 140 141 private boolean getPropertyBoolean(String key, boolean defaultValue) { 142 String s = getProperty(key); 143 if (s == null) { 144 return defaultValue; 145 } else { 146 return s.trim().equalsIgnoreCase("true"); 147 } 148 } 149 150 /** 151 * Return true if profile location enhancement is on. 152 */ 153 public boolean isEnableProfileLocation() { 154 return enableProfileLocation; 155 } 156 157 /** 158 * Return true if enhancement should automatically set labels on queries. 159 */ 160 public boolean isEnableQueryAutoLabel() { 161 return enableQueryAutoLabel; 162 } 163 164 /** 165 * Return true if this class should be scanned for transactional enhancement. 166 */ 167 public boolean detectEntityTransactionalEnhancement(String className) { 168 return filterEntityTransactional.detectEnhancement(className); 169 } 170 171 /** 172 * Return true if this class should be scanned for query bean enhancement. 173 */ 174 public boolean detectQueryBeanEnhancement(String className) { 175 return filterQueryBean.detectEnhancement(className); 176 } 177 178 /** 179 * Return true if this class should be ignored. That is JDK classes and 180 * known libraries JDBC drivers etc can be skipped. 181 */ 182 public boolean isIgnoreClass(String className) { 183 return ignoreClassHelper.isIgnoreClass(className); 184 } 185 186 /** 187 * Change the logout to something other than system out. 188 */ 189 public void setLogout(MessageOutput logout) { 190 this.logout = logout; 191 } 192 193 /** 194 * Create a new meta object for enhancing a class. 195 */ 196 public ClassMeta createClassMeta() { 197 return new ClassMeta(this, logLevel, logout); 198 } 199 200 /** 201 * Read the class meta data for a super class. 202 * <p> 203 * Typically used to read meta data for inheritance hierarchy. 204 * </p> 205 */ 206 public ClassMeta getSuperMeta(String superClassName, ClassLoader classLoader) { 207 208 try { 209 if (isIgnoreClass(superClassName)) { 210 return null; 211 } 212 return reader.get(false, superClassName, classLoader); 213 214 } catch (ClassNotFoundException e) { 215 throw new RuntimeException(e); 216 } 217 } 218 219 /** 220 * Read the class meta data for an interface. 221 * <p> 222 * Typically used to check the interface to see if it is transactional. 223 * </p> 224 */ 225 public ClassMeta getInterfaceMeta(String interfaceClassName, ClassLoader classLoader) { 226 227 try { 228 if (isIgnoreClass(interfaceClassName)) { 229 return null; 230 } 231 return reader.get(true, interfaceClassName, classLoader); 232 233 } catch (ClassNotFoundException e) { 234 throw new RuntimeException(e); 235 } 236 } 237 238 public void addClassMeta(ClassMeta meta) { 239 map.put(meta.getClassName(), meta); 240 } 241 242 public ClassMeta get(String className) { 243 return map.get(className); 244 } 245 246 /** 247 * Log some debug output. 248 */ 249 public void log(int level, String className, String msg) { 250 if (logLevel >= level) { 251 log(className, msg); 252 } 253 } 254 255 public void log(String className, String msg) { 256 if (className != null) { 257 msg = "cls: " + className + " msg: " + msg; 258 } 259 logout.println("ebean-enhance> " + msg); 260 } 261 262 public boolean isLog(int level) { 263 return logLevel >= level; 264 } 265 266 /** 267 * Log an error. 268 */ 269 public void log(Throwable e) { 270 e.printStackTrace( 271 new PrintStream(new ByteArrayOutputStream()) { 272 @Override 273 public void print(String message) { 274 logout.println(message); 275 } 276 277 @Override 278 public void println(String message) { 279 logout.println(message); 280 } 281 }); 282 } 283 284 285 /** 286 * Return the log level. 287 */ 288 public int getLogLevel() { 289 return logLevel; 290 } 291 292 /** 293 * Return true if internal ebean fields in entity classes should be transient. 294 */ 295 public boolean isTransientInternalFields() { 296 return transientInternalFields; 297 } 298 299 /** 300 * Return true if we should add null checking on *ToMany fields. 301 * <p> 302 * On getting a many that is null Ebean will create an empty List, Set or Map. If it is a 303 * ManyToMany it will turn on Modify listening. 304 * </p> 305 */ 306 public boolean isCheckNullManyFields() { 307 return checkNullManyFields; 308 } 309 310 /** 311 * Create a TransactionalMethodKey with (maybe) a profileId. 312 */ 313 public TransactionalMethodKey createMethodKey(String className, String methodName, String methodDesc, int profileId) { 314 315 TransactionalMethodKey key = new TransactionalMethodKey(className, methodName, methodDesc); 316 317 if (autoProfileId == -1) { 318 // disabled (including disabling profileIds on @Transactional) 319 key.setProfileId(0); 320 } else { 321 if (profileId == 0 && autoProfileId > 0) { 322 // enabled mode automatically setting to the next profileId 323 profileId = ++autoProfileId; 324 } 325 key.setProfileId(profileId); 326 if (profileId > 0) { 327 // we are only interested in the profiling transactions 328 profilingKeys.add(key); 329 } 330 } 331 332 return key; 333 } 334 335 /** 336 * Return the profiling transaction keys. 337 */ 338 public List<TransactionalMethodKey> getTransactionProfilingKeys() { 339 return profilingKeys; 340 } 341 342 /** 343 * Return true if transform should throw exception rather than log and return null. 344 */ 345 public boolean isThrowOnError() { 346 return throwOnError; 347 } 348 349 /** 350 * Set to true if you want transform to throw exceptions rather than return null. 351 */ 352 public void setThrowOnError(boolean throwOnError) { 353 this.throwOnError = throwOnError; 354 } 355}