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