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