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.EnhanceConstants; 012import io.ebean.enhance.common.EnhanceContext; 013import io.ebean.enhance.common.NoEnhancementRequiredException; 014import io.ebean.enhance.common.TransformRequest; 015import io.ebean.enhance.common.UrlPathHelper; 016import io.ebean.enhance.entity.ClassAdapterEntity; 017import io.ebean.enhance.entity.ClassPathClassBytesReader; 018import io.ebean.enhance.entity.MessageOutput; 019import io.ebean.enhance.querybean.TypeQueryClassAdapter; 020import io.ebean.enhance.transactional.ClassAdapterTransactional; 021import io.ebean.enhance.transactional.TransactionalMethodKey; 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 premain(String agentArgs, Instrumentation inst) { 041 042 Transformer transformer = new Transformer(null, agentArgs); 043 inst.addTransformer(transformer); 044 } 045 046 public static void agentmain(String agentArgs, Instrumentation inst) throws Exception { 047 premain(agentArgs, inst); 048 } 049 050 private final EnhanceContext enhanceContext; 051 052 private final List<CommonSuperUnresolved> unresolved = new ArrayList<>(); 053 054 private boolean keepUnresolved; 055 056 public Transformer(ClassLoader classLoader, String agentArgs) { 057 if (classLoader == null) { 058 classLoader = getClass().getClassLoader(); 059 } 060 ClassBytesReader reader = new ClassPathClassBytesReader(null); 061 AgentManifest manifest = AgentManifest.read(classLoader, null); 062 this.enhanceContext = new EnhanceContext(reader, agentArgs, manifest); 063 } 064 065 /** 066 * Create a transformer for entity bean enhancement and transactional method enhancement. 067 * 068 * @param bytesReader reads resources from class path for related inheritance and interfaces 069 * @param agentArgs command line arguments for debug level etc 070 */ 071 public Transformer(ClassBytesReader bytesReader, String agentArgs, AgentManifest manifest) { 072 this.enhanceContext = new EnhanceContext(bytesReader, agentArgs, manifest); 073 } 074 075 /** 076 * Set this to keep and report unresolved explicitly. 077 */ 078 public void setKeepUnresolved() { 079 this.keepUnresolved = true; 080 } 081 082 /** 083 * Change the logout to something other than system out. 084 */ 085 public void setLogout(MessageOutput logout) { 086 this.enhanceContext.setLogout(logout); 087 } 088 089 public void log(int level, String msg) { 090 log(level, null, msg); 091 } 092 093 private void log(int level, String className, String msg) { 094 enhanceContext.log(level, className, msg); 095 } 096 097 public int getLogLevel() { 098 return enhanceContext.getLogLevel(); 099 } 100 101 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 102 103 try { 104 // ignore JDK and JDBC classes etc 105 if (enhanceContext.isIgnoreClass(className)) { 106 log(9, className, "ignore class"); 107 return null; 108 } 109 TransformRequest request = new TransformRequest(classfileBuffer); 110 111 boolean isEbeanModel = className.equals(EnhanceConstants.EBEAN_MODEL); 112 if (isEbeanModel || enhanceContext.detectEntityTransactionalEnhancement(className)) { 113 114 DetectEnhancement detect = detect(loader, classfileBuffer); 115 116 if (detect.isEntity()) { 117 if (detect.isEnhancedEntity()) { 118 detect.log(3, "already enhanced entity"); 119 } else { 120 entityEnhancement(loader, request); 121 } 122 } 123 124 if (detect.isTransactional()) { 125 if (detect.isEnhancedTransactional()) { 126 detect.log(3, "already enhanced transactional"); 127 } else { 128 transactionalEnhancement(loader, request); 129 } 130 } 131 } 132 133 if (enhanceContext.detectQueryBeanEnhancement(className)) { 134 enhanceQueryBean(loader, request); 135 } 136 137 if (request.isEnhanced()) { 138 return request.getBytes(); 139 } 140 141 log(9, className, "no enhancement on class"); 142 return null; 143 144 } catch (NoEnhancementRequiredException e) { 145 // the class is an interface 146 log(8, className, "No Enhancement required " + e.getMessage()); 147 return null; 148 149 } catch (Exception e) { 150 enhanceContext.log(e); 151 return null; 152 } finally { 153 logUnresolvedCommonSuper(className); 154 } 155 } 156 157 /** 158 * Return the transaction profiling keys. 159 * 160 * We use these to decode a the transaction profile. 161 */ 162 public List<TransactionalMethodKey> getTransactionProfilingKeys() { 163 return enhanceContext.getTransactionProfilingKeys(); 164 } 165 166 /** 167 * Log and common superclass classpath issues that defaulted to Object. 168 */ 169 private void logUnresolvedCommonSuper(String className) { 170 if (!keepUnresolved && !unresolved.isEmpty()) { 171 for (CommonSuperUnresolved commonUnresolved : unresolved) { 172 log(0, className, commonUnresolved.getMessage()); 173 } 174 unresolved.clear(); 175 } 176 } 177 178 /** 179 * Return the list of unresolved common superclass issues. This should be cleared 180 * after each use and can only be used with {@link #setKeepUnresolved()}. 181 */ 182 public List<CommonSuperUnresolved> getUnresolved() { 183 return unresolved; 184 } 185 186 /** 187 * Perform entity bean enhancement. 188 */ 189 private void entityEnhancement(ClassLoader loader, TransformRequest request) { 190 191 ClassReader cr = new ClassReader(request.getBytes()); 192 ClassWriter cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader); 193 ClassAdapterEntity ca = new ClassAdapterEntity(cw, loader, enhanceContext); 194 try { 195 196 cr.accept(ca, ClassReader.EXPAND_FRAMES); 197 198 if (ca.isLog(1)) { 199 ca.logEnhanced(); 200 } 201 202 request.enhancedEntity(cw.toByteArray()); 203 204 } catch (AlreadyEnhancedException e) { 205 if (ca.isLog(1)) { 206 ca.log("already enhanced entity"); 207 } 208 request.enhancedEntity(null); 209 210 } catch (NoEnhancementRequiredException e) { 211 if (ca.isLog(2)) { 212 ca.log("skipping... no enhancement required"); 213 } 214 } finally { 215 unresolved.addAll(cw.getUnresolved()); 216 } 217 } 218 219 /** 220 * Perform transactional enhancement. 221 */ 222 private void transactionalEnhancement(ClassLoader loader, TransformRequest request) { 223 224 ClassReader cr = new ClassReader(request.getBytes()); 225 ClassWriter cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader); 226 ClassAdapterTransactional ca = new ClassAdapterTransactional(cw, loader, enhanceContext); 227 228 try { 229 cr.accept(ca, ClassReader.EXPAND_FRAMES); 230 231 if (ca.isLog(1)) { 232 ca.log("enhanced transactional"); 233 } 234 235 request.enhancedTransactional(cw.toByteArray()); 236 237 } catch (AlreadyEnhancedException e) { 238 if (ca.isLog(1)) { 239 ca.log("already enhanced"); 240 } 241 242 } catch (NoEnhancementRequiredException e) { 243 if (ca.isLog(0)) { 244 ca.log("skipping... no enhancement required"); 245 } 246 } finally { 247 unresolved.addAll(cw.getUnresolved()); 248 } 249 } 250 251 252 /** 253 * Perform enhancement. 254 */ 255 private void enhanceQueryBean(ClassLoader loader, TransformRequest request) { 256 257 ClassReader cr = new ClassReader(request.getBytes()); 258 ClassWriter cw = new ClassWriterWithoutClassLoading(ClassWriter.COMPUTE_FRAMES, loader); 259 TypeQueryClassAdapter ca = new TypeQueryClassAdapter(cw, enhanceContext); 260 261 try { 262 cr.accept(ca, ClassReader.EXPAND_FRAMES); 263 if (ca.isLog(9)) { 264 ca.log("... completed"); 265 } 266 request.enhancedQueryBean(cw.toByteArray()); 267 268 } catch (AlreadyEnhancedException e) { 269 if (ca.isLog(1)) { 270 ca.log("already enhanced"); 271 } 272 273 } catch (NoEnhancementRequiredException e) { 274 if (ca.isLog(9)) { 275 ca.log("... skipping, no enhancement required"); 276 } 277 } finally { 278 unresolved.addAll(cw.getUnresolved()); 279 } 280 } 281 282 /** 283 * Helper method to split semi-colon separated class paths into a URL array. 284 */ 285 public static URL[] parseClassPaths(String extraClassPath) { 286 287 if (extraClassPath == null) { 288 return new URL[0]; 289 } 290 291 return UrlPathHelper.convertToUrl(extraClassPath.split(";")); 292 } 293 294 /** 295 * Read the bytes quickly trying to detect if it needs entity or transactional 296 * enhancement. 297 */ 298 private DetectEnhancement detect(ClassLoader classLoader, byte[] classfileBuffer) { 299 300 DetectEnhancement detect = new DetectEnhancement(classLoader, enhanceContext); 301 302 ClassReader cr = new ClassReader(classfileBuffer); 303 cr.accept(detect, ClassReader.SKIP_CODE + ClassReader.SKIP_DEBUG + ClassReader.SKIP_FRAMES); 304 return detect; 305 } 306}