001package io.ebean.enhance; 002 003import io.ebean.enhance.asm.ClassReader; 004import io.ebean.enhance.asm.ClassWriter; 005import io.ebean.enhance.asm.ClassWriterWithoutClassLoading; 006import io.ebean.enhance.common.AgentManifest; 007import io.ebean.enhance.common.AlreadyEnhancedException; 008import io.ebean.enhance.common.ClassBytesReader; 009import io.ebean.enhance.common.CommonSuperUnresolved; 010import io.ebean.enhance.common.DetectEnhancement; 011import io.ebean.enhance.common.EnhanceContext; 012import io.ebean.enhance.common.NoEnhancementRequiredException; 013import io.ebean.enhance.common.TransformRequest; 014import io.ebean.enhance.common.UrlPathHelper; 015import io.ebean.enhance.entity.ClassAdapterEntity; 016import io.ebean.enhance.entity.ClassPathClassBytesReader; 017import io.ebean.enhance.entity.MessageOutput; 018import io.ebean.enhance.querybean.TypeQueryClassAdapter; 019import io.ebean.enhance.transactional.ClassAdapterTransactional; 020import io.ebean.enhance.transactional.TransactionalMethodKey; 021import org.avaje.agentloader.AgentLoader; 022 023import java.lang.instrument.ClassFileTransformer; 024import java.lang.instrument.IllegalClassFormatException; 025import java.lang.instrument.Instrumentation; 026import java.net.URL; 027import java.security.ProtectionDomain; 028import java.util.ArrayList; 029import java.util.List; 030 031/** 032 * A Class file Transformer that performs Ebean enhancement of entity beans, 033 * transactional methods and query bean enhancement. 034 * <p> 035 * This is used as both a java agent or via Maven and Gradle plugins etc. 036 * </p> 037 */ 038public class Transformer implements ClassFileTransformer { 039 040 public static void agentmain(String agentArgs, Instrumentation inst) { 041 premain(agentArgs, inst); 042 } 043 044 public static void premain(String agentArgs, Instrumentation inst) { 045 046 instrumentation = inst; 047 transformer = new Transformer(null, agentArgs); 048 inst.addTransformer(transformer); 049 } 050 051 private static Instrumentation instrumentation; 052 053 private static Transformer transformer; 054 055 private final EnhanceContext enhanceContext; 056 057 private final List<CommonSuperUnresolved> unresolved = new ArrayList<>(); 058 059 private boolean keepUnresolved; 060 061 public Transformer(ClassLoader classLoader, String agentArgs) { 062 if (classLoader == null) { 063 classLoader = getClass().getClassLoader(); 064 } 065 ClassBytesReader reader = new ClassPathClassBytesReader(null); 066 AgentManifest manifest = AgentManifest.read(classLoader, null); 067 this.enhanceContext = new EnhanceContext(reader, agentArgs, manifest); 068 } 069 070 /** 071 * Create with an EnhancementContext (for IDE Plugins mainly) 072 */ 073 public Transformer(EnhanceContext enhanceContext) { 074 this.enhanceContext = enhanceContext; 075 } 076 077 /** 078 * Create a transformer for entity bean enhancement and transactional method enhancement. 079 * 080 * @param bytesReader reads resources from class path for related inheritance and interfaces 081 * @param agentArgs command line arguments for debug level etc 082 */ 083 public Transformer(ClassBytesReader bytesReader, String agentArgs, AgentManifest manifest) { 084 this.enhanceContext = new EnhanceContext(bytesReader, agentArgs, manifest); 085 } 086 087 /** 088 * Return the Instrumentation instance. 089 */ 090 public static Instrumentation instrumentation() { 091 verifyInitialization(); 092 return instrumentation; 093 } 094 095 /** 096 * Return the Transformer instance. 097 */ 098 public static Transformer get() { 099 verifyInitialization(); 100 return transformer; 101 } 102 103 /** 104 * Use agent loader if necessary to initialise the transformer. 105 */ 106 public static void verifyInitialization() { 107 if (instrumentation == null) { 108 if (!AgentLoader.loadAgentFromClasspath("ebean-agent", "debug=1")) { 109 throw new IllegalStateException("ebean-agent not found in classpath - not dynamically loaded"); 110 } 111 } 112 } 113 114 /** 115 * Set this to keep and report unresolved explicitly. 116 */ 117 public void setKeepUnresolved() { 118 this.keepUnresolved = true; 119 } 120 121 /** 122 * Change the logout to something other than system out. 123 */ 124 public void setLogout(MessageOutput logout) { 125 this.enhanceContext.setLogout(logout); 126 } 127 128 public void log(int level, String msg) { 129 log(level, null, msg); 130 } 131 132 private void log(int level, String className, String msg) { 133 enhanceContext.log(level, className, msg); 134 } 135 136 public int getLogLevel() { 137 return enhanceContext.getLogLevel(); 138 } 139 140 @Override 141 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 142 143 try { 144 // ignore JDK and JDBC classes etc 145 if (enhanceContext.isIgnoreClass(className)) { 146 log(9, className, "ignore class"); 147 return null; 148 } 149 TransformRequest request = new TransformRequest(className, classfileBuffer); 150 151 if (enhanceContext.detectEntityTransactionalEnhancement(className)) { 152 enhanceEntityAndTransactional(loader, request); 153 } 154 155 if (enhanceContext.detectQueryBeanEnhancement(className)) { 156 enhanceQueryBean(loader, request); 157 } 158 159 if (request.isEnhanced()) { 160 return request.getBytes(); 161 } 162 163 log(9, className, "no enhancement on class"); 164 return null; 165 166 } catch (NoEnhancementRequiredException e) { 167 // the class is an interface 168 log(8, className, "No Enhancement required " + e.getMessage()); 169 return null; 170 171 } catch (Exception e) { 172 if (enhanceContext.isThrowOnError()) { 173 throw new IllegalStateException(e); 174 } 175 enhanceContext.log(e); 176 return null; 177 } finally { 178 logUnresolvedCommonSuper(className); 179 } 180 } 181 182 /** 183 * Perform entity and transactional enhancement. 184 */ 185 private void enhanceEntityAndTransactional(ClassLoader loader, TransformRequest request) { 186 try { 187 DetectEnhancement detect = detect(loader, request.getBytes()); 188 if (detect.isEntity()) { 189 if (detect.isEnhancedEntity()) { 190 detect.log(3, "already enhanced entity"); 191 } else { 192 entityEnhancement(loader, request); 193 } 194 } 195 if (enhanceContext.isEnableProfileLocation() || detect.isTransactional()) { 196 if (detect.isEnhancedTransactional()) { 197 detect.log(3, "already enhanced transactional"); 198 } else { 199 transactionalEnhancement(loader, request); 200 } 201 } 202 } catch (NoEnhancementRequiredException e) { 203 log(8, request.getClassName(), "No entity or transactional enhancement required " + e.getMessage()); 204 } 205 } 206 207 /** 208 * Return the transaction profiling keys. 209 * 210 * We use these to decode a the transaction profile. 211 */ 212 public List<TransactionalMethodKey> getTransactionProfilingKeys() { 213 return enhanceContext.getTransactionProfilingKeys(); 214 } 215 216 /** 217 * Log and common superclass classpath issues that defaulted to Object. 218 */ 219 private void logUnresolvedCommonSuper(String className) { 220 if (!keepUnresolved && !unresolved.isEmpty()) { 221 for (CommonSuperUnresolved commonUnresolved : unresolved) { 222 log(0, className, commonUnresolved.getMessage()); 223 } 224 unresolved.clear(); 225 } 226 } 227 228 /** 229 * Return the list of unresolved common superclass issues. This should be cleared 230 * after each use and can only be used with {@link #setKeepUnresolved()}. 231 */ 232 public List<CommonSuperUnresolved> getUnresolved() { 233 return unresolved; 234 } 235 236 /** 237 * Perform entity bean enhancement. 238 */ 239 private void entityEnhancement(ClassLoader loader, TransformRequest request) { 240 241 ClassReader cr = new ClassReader(request.getBytes()); 242 ClassWriterWithoutClassLoading cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader); 243 ClassAdapterEntity ca = new ClassAdapterEntity(cw, loader, enhanceContext); 244 try { 245 246 cr.accept(ca, ClassReader.EXPAND_FRAMES); 247 248 if (ca.isLog(1)) { 249 ca.logEnhanced(); 250 } 251 252 request.enhancedEntity(cw.toByteArray()); 253 254 } catch (AlreadyEnhancedException e) { 255 if (ca.isLog(2)) { 256 ca.log("already enhanced entity"); 257 } 258 request.enhancedEntity(null); 259 260 } catch (NoEnhancementRequiredException e) { 261 if (ca.isLog(3)) { 262 ca.log("skipping... no enhancement required"); 263 } 264 } finally { 265 unresolved.addAll(cw.getUnresolved()); 266 } 267 } 268 269 /** 270 * Perform transactional enhancement and Finder profileLocation enhancement. 271 */ 272 private void transactionalEnhancement(ClassLoader loader, TransformRequest request) { 273 274 ClassReader cr = new ClassReader(request.getBytes()); 275 ClassWriterWithoutClassLoading cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader); 276 ClassAdapterTransactional ca = new ClassAdapterTransactional(cw, loader, enhanceContext); 277 278 try { 279 cr.accept(ca, ClassReader.EXPAND_FRAMES); 280 281 if (ca.isLog(1)) { 282 ca.log("enhanced transactional"); 283 } 284 285 request.enhancedTransactional(cw.toByteArray()); 286 287 } catch (AlreadyEnhancedException e) { 288 if (ca.isLog(3)) { 289 ca.log("already enhanced"); 290 } 291 292 } catch (NoEnhancementRequiredException e) { 293 if (ca.isLog(3)) { 294 ca.log("skipping... no enhancement required"); 295 } 296 } finally { 297 unresolved.addAll(cw.getUnresolved()); 298 } 299 } 300 301 302 /** 303 * Perform enhancement. 304 */ 305 private void enhanceQueryBean(ClassLoader loader, TransformRequest request) { 306 307 ClassReader cr = new ClassReader(request.getBytes()); 308 ClassWriterWithoutClassLoading cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader); 309 TypeQueryClassAdapter ca = new TypeQueryClassAdapter(cw, enhanceContext); 310 311 try { 312 cr.accept(ca, ClassReader.EXPAND_FRAMES); 313 if (ca.isLog(9)) { 314 ca.log("... completed"); 315 } 316 request.enhancedQueryBean(cw.toByteArray()); 317 318 } catch (AlreadyEnhancedException e) { 319 if (ca.isLog(1)) { 320 ca.log("already enhanced"); 321 } 322 323 } catch (NoEnhancementRequiredException e) { 324 if (ca.isLog(9)) { 325 ca.log("... skipping, no enhancement required"); 326 } 327 } finally { 328 unresolved.addAll(cw.getUnresolved()); 329 } 330 } 331 332 /** 333 * Helper method to split semi-colon separated class paths into a URL array. 334 */ 335 public static URL[] parseClassPaths(String extraClassPath) { 336 337 if (extraClassPath == null) { 338 return new URL[0]; 339 } 340 341 return UrlPathHelper.convertToUrl(extraClassPath.split(";")); 342 } 343 344 /** 345 * Read the bytes quickly trying to detect if it needs entity or transactional 346 * enhancement. 347 */ 348 private DetectEnhancement detect(ClassLoader classLoader, byte[] classfileBuffer) { 349 350 DetectEnhancement detect = new DetectEnhancement(classLoader, enhanceContext); 351 352 ClassReader cr = new ClassReader(classfileBuffer); 353 cr.accept(detect, ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES); 354 return detect; 355 } 356}