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 /** 053 * Mapping of profileId to transactional method descriptions (for decoding profiling). 054 */ 055 private final List<TransactionalMethodKey> profilingKeys = new ArrayList(); 056 057 /** 058 * Construct a context for enhancement. 059 */ 060 public EnhanceContext(ClassBytesReader classBytesReader, String agentArgs, AgentManifest manifest) { 061 062 this.autoProfileId = manifest.transactionProfilingStart(); 063 064 this.agentArgsMap = ArgParser.parse(agentArgs); 065 this.filterEntityTransactional = new FilterEntityTransactional(manifest); 066 this.filterQueryBean = new FilterQueryBean(manifest); 067 068 this.detectQueryBean = Distill.convert(manifest.getEntityPackages()); 069 if (detectQueryBean.isEmpty()) { 070 logger.log(Level.FINE, "No ebean.mf detected"); 071 } 072 073 this.ignoreClassHelper = new IgnoreClassHelper(); 074 this.logout = new SysoutMessageOutput(System.out); 075 this.classBytesReader = classBytesReader; 076 this.reader = new ClassMetaReader(this); 077 078 String debugValue = agentArgsMap.get("debug"); 079 if (debugValue != null) { 080 try { 081 logLevel = Integer.parseInt(debugValue); 082 } catch (NumberFormatException e) { 083 logger.log(Level.WARNING, "Agent debug argument [" + debugValue + "] is not an int?"); 084 } 085 } 086 087 this.transientInternalFields = getPropertyBoolean("transientInternalFields", manifest.isTransientInternalFields()); 088 this.checkNullManyFields = getPropertyBoolean("checkNullManyFields", manifest.isCheckNullManyFields()); 089 } 090 091 public byte[] getClassBytes(String className, ClassLoader classLoader) { 092 return classBytesReader.getClassBytes(className, classLoader); 093 } 094 095 /** 096 * Return true if the owner class is a type query bean. 097 * <p> 098 * If true typically means the caller needs to change GETFIELD calls to instead invoke the generated 099 * 'property access' methods. 100 * </p> 101 */ 102 public boolean isQueryBean(String owner) { 103 return detectQueryBean.isQueryBean(owner); 104 } 105 106 /** 107 * Return a value from the entity arguments using its key. 108 */ 109 public String getProperty(String key) { 110 return agentArgsMap.get(key.toLowerCase()); 111 } 112 113 public boolean getPropertyBoolean(String key, boolean defaultValue) { 114 String s = getProperty(key); 115 if (s == null) { 116 return defaultValue; 117 } else { 118 return s.trim().equalsIgnoreCase("true"); 119 } 120 } 121 122 /** 123 * Return true if this class should be scanned for transactional enhancement. 124 */ 125 public boolean detectEntityTransactionalEnhancement(String className) { 126 return filterEntityTransactional.detectEnhancement(className); 127 } 128 129 /** 130 * Return true if this class should be scanned for query bean enhancement. 131 */ 132 public boolean detectQueryBeanEnhancement(String className) { 133 return filterQueryBean.detectEnhancement(className); 134 } 135 136 /** 137 * Return true if this class should be ignored. That is JDK classes and 138 * known libraries JDBC drivers etc can be skipped. 139 */ 140 public boolean isIgnoreClass(String className) { 141 return ignoreClassHelper.isIgnoreClass(className); 142 } 143 144 /** 145 * Change the logout to something other than system out. 146 */ 147 public void setLogout(MessageOutput logout) { 148 this.logout = logout; 149 } 150 151 /** 152 * Create a new meta object for enhancing a class. 153 */ 154 public ClassMeta createClassMeta() { 155 return new ClassMeta(this, logLevel, logout); 156 } 157 158 /** 159 * Read the class meta data for a super class. 160 * <p> 161 * Typically used to read meta data for inheritance hierarchy. 162 * </p> 163 */ 164 public ClassMeta getSuperMeta(String superClassName, ClassLoader classLoader) { 165 166 try { 167 if (isIgnoreClass(superClassName)) { 168 return null; 169 } 170 return reader.get(false, superClassName, classLoader); 171 172 } catch (ClassNotFoundException e) { 173 throw new RuntimeException(e); 174 } 175 } 176 177 /** 178 * Read the class meta data for an interface. 179 * <p> 180 * Typically used to check the interface to see if it is transactional. 181 * </p> 182 */ 183 public ClassMeta getInterfaceMeta(String interfaceClassName, ClassLoader classLoader) { 184 185 try { 186 if (isIgnoreClass(interfaceClassName)) { 187 return null; 188 } 189 return reader.get(true, interfaceClassName, classLoader); 190 191 } catch (ClassNotFoundException e) { 192 throw new RuntimeException(e); 193 } 194 } 195 196 public void addClassMeta(ClassMeta meta) { 197 map.put(meta.getClassName(), meta); 198 } 199 200 public ClassMeta get(String className) { 201 return map.get(className); 202 } 203 204 /** 205 * Log some debug output. 206 */ 207 public void log(int level, String className, String msg) { 208 if (logLevel >= level) { 209 log(className, msg); 210 } 211 } 212 213 public void log(String className, String msg) { 214 if (className != null) { 215 msg = "cls: " + className + " msg: " + msg; 216 } 217 logout.println("ebean-enhance> " + msg); 218 } 219 220 public boolean isLog(int level) { 221 return logLevel >= level; 222 } 223 224 /** 225 * Log an error. 226 */ 227 public void log(Throwable e) { 228 e.printStackTrace( 229 new PrintStream(new ByteArrayOutputStream()) { 230 @Override 231 public void print(String message) { 232 logout.println(message); 233 } 234 235 @Override 236 public void println(String message) { 237 logout.println(message); 238 } 239 }); 240 } 241 242 243 /** 244 * Return the log level. 245 */ 246 public int getLogLevel() { 247 return logLevel; 248 } 249 250 /** 251 * Return true if internal ebean fields in entity classes should be transient. 252 */ 253 public boolean isTransientInternalFields() { 254 return transientInternalFields; 255 } 256 257 /** 258 * Return true if we should add null checking on *ToMany fields. 259 * <p> 260 * On getting a many that is null Ebean will create an empty List, Set or Map. If it is a 261 * ManyToMany it will turn on Modify listening. 262 * </p> 263 */ 264 public boolean isCheckNullManyFields() { 265 return checkNullManyFields; 266 } 267 268 /** 269 * Create a TransactionalMethodKey with (maybe) a profileId. 270 */ 271 public TransactionalMethodKey createMethodKey(String className, String methodName, String methodDesc, int profileId) { 272 273 TransactionalMethodKey key = new TransactionalMethodKey(className, methodName, methodDesc); 274 275 if (autoProfileId == -1) { 276 // disabled (including disabling profileIds on @Transactional) 277 key.setProfileId(0); 278 } else { 279 if (profileId == 0 && autoProfileId > 0) { 280 // enabled mode automatically setting to the next profileId 281 profileId = ++autoProfileId; 282 } 283 key.setProfileId(profileId); 284 if (profileId > 0) { 285 // we are only interested in the profiling transactions 286 profilingKeys.add(key); 287 } 288 } 289 290 return key; 291 } 292 293 /** 294 * Return the profiling transaction keys. 295 */ 296 public List<TransactionalMethodKey> getTransactionProfilingKeys() { 297 return profilingKeys; 298 } 299}