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