001package io.ebean.enhance.transactional; 002 003import io.ebean.enhance.asm.AnnotationVisitor; 004import io.ebean.enhance.asm.ClassVisitor; 005import io.ebean.enhance.asm.FieldVisitor; 006import io.ebean.enhance.asm.Label; 007import io.ebean.enhance.asm.MethodVisitor; 008import io.ebean.enhance.asm.Opcodes; 009import io.ebean.enhance.common.AlreadyEnhancedException; 010import io.ebean.enhance.common.AnnotationInfo; 011import io.ebean.enhance.common.AnnotationInfoVisitor; 012import io.ebean.enhance.common.ClassMeta; 013import io.ebean.enhance.common.EnhanceConstants; 014import io.ebean.enhance.common.EnhanceContext; 015import io.ebean.enhance.common.NoEnhancementRequiredException; 016 017import java.util.ArrayList; 018import java.util.LinkedHashMap; 019import java.util.LinkedHashSet; 020import java.util.List; 021import java.util.Map; 022import java.util.Set; 023import java.util.logging.Level; 024import java.util.logging.Logger; 025 026import static io.ebean.enhance.asm.Opcodes.ACC_PRIVATE; 027import static io.ebean.enhance.asm.Opcodes.ACC_STATIC; 028import static io.ebean.enhance.asm.Opcodes.BIPUSH; 029import static io.ebean.enhance.asm.Opcodes.INVOKESTATIC; 030import static io.ebean.enhance.asm.Opcodes.PUTSTATIC; 031import static io.ebean.enhance.asm.Opcodes.RETURN; 032import static io.ebean.enhance.common.EnhanceConstants.CLINIT; 033import static io.ebean.enhance.common.EnhanceConstants.INIT; 034import static io.ebean.enhance.common.EnhanceConstants.NOARG_VOID; 035 036/** 037 * ClassAdapter used to add transactional support. 038 */ 039public class ClassAdapterTransactional extends ClassVisitor { 040 041 private static final Logger logger = Logger.getLogger(ClassAdapterTransactional.class.getName()); 042 043 static final String QP_FIELD_PREFIX = "_$ebpq"; 044 045 static final String TX_FIELD_PREFIX = "_$ebpt"; 046 047 private static final String IO_EBEAN_FINDER = "io/ebean/Finder"; 048 049 private static final String $_COMPANION = "$Companion"; 050 051 private static final String INIT_PROFILE_LOCATIONS = "_$initProfileLocations"; 052 private static final String LKOTLIN_METADATA = "Lkotlin/Metadata;"; 053 private static final String _$EBP = "_$ebp"; 054 private static final String LIO_EBEAN_PROFILE_LOCATION = "Lio/ebean/ProfileLocation;"; 055 056 private final Set<String> transactionalMethods = new LinkedHashSet<>(); 057 058 private final Set<Integer> transactionalLineNumbers = new LinkedHashSet<>(); 059 060 private final EnhanceContext enhanceContext; 061 062 private final ClassLoader classLoader; 063 064 private ArrayList<ClassMeta> transactionalInterfaces = new ArrayList<>(); 065 066 /** 067 * Class level annotation information. 068 */ 069 private AnnotationInfo classAnnotationInfo; 070 071 private String className; 072 073 private boolean markAsKotlin; 074 075 private boolean existingStaticInitialiser; 076 077 private boolean finder; 078 079 private int queryProfileCount; 080 081 private int transactionProfileCount; 082 083 private Map<Integer, String> txLabels = new LinkedHashMap<>(); 084 085 public ClassAdapterTransactional(ClassVisitor cv, ClassLoader classLoader, EnhanceContext context) { 086 super(Opcodes.ASM7, cv); 087 this.classLoader = classLoader; 088 this.enhanceContext = context; 089 } 090 091 public String className() { 092 return className; 093 } 094 095 public boolean isLog(int level) { 096 return enhanceContext.isLog(level); 097 } 098 099 public void log(String msg) { 100 enhanceContext.log(className, msg); 101 } 102 103 public boolean isQueryBean(String owner) { 104 return enhanceContext.isQueryBean(owner); 105 } 106 107 public AnnotationInfo getClassAnnotationInfo() { 108 return classAnnotationInfo; 109 } 110 111 /** 112 * Returns Transactional information from a matching interface method. 113 * <p> 114 * Returns null if no matching (transactional) interface method was found. 115 * </p> 116 */ 117 public AnnotationInfo getInterfaceTransactionalInfo(String methodName, String methodDesc) { 118 119 AnnotationInfo interfaceAnnotationInfo = null; 120 121 for (int i = 0; i < transactionalInterfaces.size(); i++) { 122 ClassMeta interfaceMeta = transactionalInterfaces.get(i); 123 AnnotationInfo ai = interfaceMeta.getInterfaceTransactionalInfo(methodName, methodDesc); 124 if (ai != null) { 125 if (interfaceAnnotationInfo != null) { 126 String msg = "Error in [" + className + "] searching the transactional interfaces [" 127 + transactionalInterfaces + "] found more than one match for the transactional method:" 128 + methodName + " " + methodDesc; 129 130 logger.log(Level.SEVERE, msg); 131 132 } else { 133 interfaceAnnotationInfo = ai; 134 if (isLog(2)) { 135 log("inherit transactional from interface [" + interfaceMeta + "] method[" + methodName + " " + methodDesc + "]"); 136 } 137 } 138 } 139 } 140 141 return interfaceAnnotationInfo; 142 } 143 144 /** 145 * Visit the class with interfaces. 146 */ 147 @Override 148 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { 149 150 className = name; 151 finder = superName.equals(IO_EBEAN_FINDER); 152 153 // Note: interfaces can be an empty array but not null 154 int n = 1 + interfaces.length; 155 String[] newInterfaces = new String[n]; 156 for (int i = 0; i < interfaces.length; i++) { 157 newInterfaces[i] = interfaces[i]; 158 if (newInterfaces[i].equals(EnhanceConstants.C_ENHANCEDTRANSACTIONAL)) { 159 throw new AlreadyEnhancedException(name); 160 } 161 ClassMeta interfaceMeta = enhanceContext.getInterfaceMeta(newInterfaces[i], classLoader); 162 if (interfaceMeta != null && interfaceMeta.isTransactional()) { 163 // the interface was transactional. We gather its information 164 // because our methods inherit that transactional configuration 165 transactionalInterfaces.add(interfaceMeta); 166 167 if (isLog(6)) { 168 log(" implements transactional interface " + interfaceMeta.getDescription()); 169 } 170 } 171 } 172 173 // Add the EnhancedTransactional interface 174 newInterfaces[newInterfaces.length - 1] = EnhanceConstants.C_ENHANCEDTRANSACTIONAL; 175 176 super.visit(version, access, name, signature, superName, newInterfaces); 177 } 178 179 /** 180 * Visit class level annotations. 181 */ 182 @Override 183 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 184 185 if (LKOTLIN_METADATA.equals(desc)) { 186 markAsKotlin = true; 187 } 188 189 AnnotationVisitor av = super.visitAnnotation(desc, visible); 190 191 if (desc.equals(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION)) { 192 // we have class level Transactional annotation 193 // which will act as default for all methods in this class 194 classAnnotationInfo = new AnnotationInfo(null); 195 return new AnnotationInfoVisitor(null, classAnnotationInfo, av); 196 197 } else { 198 return av; 199 } 200 } 201 202 @Override 203 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { 204 if (name.startsWith(_$EBP) && desc.equals(LIO_EBEAN_PROFILE_LOCATION)) { 205 throw new AlreadyEnhancedException(className); 206 } 207 return super.visitField(access, name, desc, signature, value); 208 } 209 210 /** 211 * Visit the methods specifically looking for method level transactional 212 * annotations. 213 */ 214 @Override 215 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 216 217 MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); 218 if (name.equals(INIT_PROFILE_LOCATIONS)) { 219 throw new AlreadyEnhancedException(className); 220 } 221 if (name.equals(INIT)) { 222 if (!kotlinCompanion() && enhanceContext.isEnableProfileLocation()) { 223 // enhance constructors containing query bean queries with profile location 224 if (isLog(4)) { 225 log("enhance constructor with profile location on className:" + className); 226 } 227 return new ConstructorMethodAdapter(this, mv, access, name, desc); 228 } 229 return mv; 230 } 231 if (name.equals(CLINIT)) { 232 if (!enhanceContext.isEnableProfileLocation()) { 233 // not enhancing class static initialiser 234 return mv; 235 } else { 236 if (isLog(3)) { 237 log("... <clinit> exists - adding call to _$initProfileLocations()"); 238 } 239 existingStaticInitialiser = true; 240 return new StaticInitAdapter(mv, access, name, desc, className); 241 } 242 } 243 244 return new MethodAdapter(this, mv, access, name, desc); 245 } 246 247 private boolean kotlinCompanion() { 248 return markAsKotlin && className.endsWith($_COMPANION); 249 } 250 251 @Override 252 public void visitEnd() { 253 if (queryProfileCount == 0 && transactionProfileCount == 0) { 254 throw new NoEnhancementRequiredException(className); 255 } 256 if (isLog(2)) { 257 log("methods:" + transactionalMethods + " qp:" + queryProfileCount + " tp:" + transactionProfileCount + " profileLocation:" + isEnableProfileLocation()); 258 } 259 if (enhanceContext.isEnableProfileLocation()) { 260 addStaticFieldDefinitions(); 261 addStaticFieldInitialisers(); 262 if (!existingStaticInitialiser) { 263 if (isLog(5)) { 264 log("... add <clinit> to call _$initProfileLocations()"); 265 } 266 addStaticInitialiser(); 267 } 268 } 269 super.visitEnd(); 270 } 271 272 private void addStaticFieldDefinitions() { 273 for (int i = 0; i < queryProfileCount; i++) { 274 FieldVisitor fv = cv.visitField(ACC_PRIVATE + ACC_STATIC, QP_FIELD_PREFIX + i, "Lio/ebean/ProfileLocation;", null, null); 275 fv.visitEnd(); 276 } 277 for (int i = 0; i < transactionProfileCount; i++) { 278 FieldVisitor fv = cv.visitField(ACC_PRIVATE + ACC_STATIC, TX_FIELD_PREFIX + i, "Lio/ebean/ProfileLocation;", null, null); 279 fv.visitEnd(); 280 } 281 } 282 283 private void addStaticFieldInitialisers() { 284 MethodVisitor mv = cv.visitMethod(ACC_PRIVATE + ACC_STATIC, "_$initProfileLocations", NOARG_VOID, null, null); 285 mv.visitCode(); 286 287 for (int i = 0; i < queryProfileCount; i++) { 288 Label l0 = new Label(); 289 mv.visitLabel(l0); 290 mv.visitLineNumber(1, l0); 291 mv.visitMethodInsn(INVOKESTATIC, "io/ebean/ProfileLocation", "create", "()Lio/ebean/ProfileLocation;", true); 292 mv.visitFieldInsn(PUTSTATIC, className, QP_FIELD_PREFIX + i, "Lio/ebean/ProfileLocation;"); 293 } 294 295 boolean withLineNumbers = (transactionProfileCount == transactionalLineNumbers.size()); 296 List<Integer> lineNumbers = new ArrayList<>(transactionalLineNumbers); 297 298 for (int i = 0; i < transactionProfileCount; i++) { 299 Label l0 = new Label(); 300 mv.visitLabel(l0); 301 mv.visitLineNumber(2, l0); 302 if (withLineNumbers) { 303 int txnLineNumber = lineNumbers.get(i); 304 mv.visitIntInsn(BIPUSH, txnLineNumber); 305 String label = getTxnLabel(i); 306 mv.visitLdcInsn(label); 307 mv.visitMethodInsn(INVOKESTATIC, "io/ebean/ProfileLocation", "create", "(ILjava/lang/String;)Lio/ebean/ProfileLocation;", true); 308 309 } else { 310 mv.visitMethodInsn(INVOKESTATIC, "io/ebean/ProfileLocation", "create", "()Lio/ebean/ProfileLocation;", true); 311 } 312 mv.visitFieldInsn(PUTSTATIC, className, TX_FIELD_PREFIX + i, "Lio/ebean/ProfileLocation;"); 313 } 314 315 Label l1 = new Label(); 316 mv.visitLabel(l1); 317 mv.visitLineNumber(3, l1); 318 mv.visitInsn(RETURN); 319 mv.visitMaxs(1, 0); 320 mv.visitEnd(); 321 } 322 323 private String getTxnLabel(int i) { 324 String label = txLabels.get(i); 325 return (label != null) ? label : ""; 326 } 327 328 /** 329 * Add a static initialization block when there was not one on the class. 330 */ 331 private void addStaticInitialiser() { 332 333 MethodVisitor mv = cv.visitMethod(ACC_STATIC, CLINIT, NOARG_VOID, null, null); 334 mv.visitCode(); 335 Label l0 = new Label(); 336 mv.visitLabel(l0); 337 mv.visitLineNumber(4, l0); 338 mv.visitMethodInsn(INVOKESTATIC, className, INIT_PROFILE_LOCATIONS, NOARG_VOID, false); 339 Label l1 = new Label(); 340 mv.visitLabel(l1); 341 mv.visitLineNumber(5, l1); 342 mv.visitInsn(RETURN); 343 mv.visitMaxs(0, 0); 344 mv.visitEnd(); 345 } 346 347 void transactionalMethod(TransactionalMethodKey methodKey) { 348 349 transactionalLineNumbers.add(methodKey.getLineNumber()); 350 transactionalMethods.add(methodKey.getMethodName()); 351 if (isLog(3)) { 352 log("method - " + methodKey); 353 } 354 } 355 356 /** 357 * Create and return the TransactionalMethodKey. 358 * <p> 359 * Takes into account the profiling mode (as per manifest) and explicit profileId. 360 */ 361 public TransactionalMethodKey createMethodKey(String methodName, String methodDesc, int profId) { 362 return enhanceContext.createMethodKey(className, methodName, methodDesc, profId); 363 } 364 365 /** 366 * Return true if profile location enhancement is on. 367 */ 368 public boolean isEnableProfileLocation() { 369 return enhanceContext.isEnableProfileLocation(); 370 } 371 372 /** 373 * Return the next index for query profile location. 374 */ 375 int nextQueryProfileLocation() { 376 return queryProfileCount++; 377 } 378 379 /** 380 * Return the next index for transaction profile location. 381 */ 382 int nextTransactionLocation() { 383 return transactionProfileCount++; 384 } 385 386 /** 387 * Return true if this enhancing class extends Ebean Finder. 388 */ 389 public boolean isFinder() { 390 return finder; 391 } 392 393 /** 394 * Set the transaction label for a given index. 395 */ 396 public void putTxnLabel(int locationField, String txLabel) { 397 txLabels.put(locationField, txLabel); 398 } 399}