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