001package io.ebean.enhance.transactional; 002 003import io.ebean.enhance.asm.AnnotationVisitor; 004import io.ebean.enhance.asm.Label; 005import io.ebean.enhance.asm.MethodVisitor; 006import io.ebean.enhance.asm.Opcodes; 007import io.ebean.enhance.asm.Type; 008import io.ebean.enhance.asm.commons.FinallyAdapter; 009import io.ebean.enhance.common.AnnotationInfo; 010import io.ebean.enhance.common.AnnotationInfoVisitor; 011import io.ebean.enhance.common.EnhanceConstants; 012import io.ebean.enhance.common.VisitUtil; 013 014import java.util.ArrayList; 015 016/** 017 * Adapts a method to support Transactional. 018 * <p> 019 * Adds a TxScope and ScopeTrans local variables. On normal exit makes a call 020 * out via InternalServer to end the scopeTrans depending on the exit type 021 * opcode (ATHROW vs ARETURN etc) and whether particular throwable's cause a 022 * rollback or not. 023 * </p> 024 */ 025public class ScopeTransAdapter extends FinallyAdapter implements EnhanceConstants { 026 027 private static final Type txScopeType = Type.getType("L"+C_TXSCOPE+";"); 028 private static final Type helpScopeTrans = Type.getType(L_HELPSCOPETRANS); 029 030 private final AnnotationInfo annotationInfo; 031 032 private final ClassAdapterTransactional owner; 033 034 private final String methodName; 035 036 private boolean transactional; 037 038 private int posTxScope; 039 private int lineNumber; 040 private TransactionalMethodKey methodKey; 041 042 ScopeTransAdapter(ClassAdapterTransactional owner, final MethodVisitor mv, final int access, final String name, final String desc) { 043 super(Opcodes.ASM6, mv, access, name, desc); 044 this.owner = owner; 045 this.methodName = name; 046 047 // inherit from class level Transactional annotation 048 AnnotationInfo parentInfo = owner.getClassAnnotationInfo(); 049 050 // inherit from interface method transactional annotation 051 AnnotationInfo interfaceInfo = owner.getInterfaceTransactionalInfo(name, desc); 052 if (parentInfo == null){ 053 parentInfo = interfaceInfo; 054 } else { 055 parentInfo.setParent(interfaceInfo); 056 } 057 058 // inherit transactional annotations from parentInfo 059 annotationInfo = new AnnotationInfo(parentInfo); 060 061 // default based on whether Transactional annotation 062 // is at the class level or on interface method 063 transactional = parentInfo != null; 064 } 065 066 @Override 067 public void visitLineNumber(int line, Label start) { 068 super.visitLineNumber(line, start); 069 if (lineNumber == 0 && methodKey != null) { 070 lineNumber = line; 071 methodKey.setLineNumber(lineNumber); 072 } 073 } 074 075 @Override 076 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 077 AnnotationVisitor av = super.visitAnnotation(desc, visible); 078 if (desc.equals(AVAJE_TRANSACTIONAL_ANNOTATION)) { 079 transactional = true; 080 return new AnnotationInfoVisitor(null, annotationInfo, av); 081 } else { 082 return av; 083 } 084 } 085 086 private void setTxType(Object txType){ 087 088 mv.visitVarInsn(ALOAD, posTxScope); 089 mv.visitLdcInsn(txType.toString()); 090 mv.visitMethodInsn(INVOKESTATIC, C_TXTYPE, "valueOf", "(Ljava/lang/String;)L"+C_TXTYPE+";", false); 091 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setType", "(L"+C_TXTYPE+";)L"+C_TXSCOPE+";", false); 092 mv.visitInsn(POP); 093 } 094 095 private void setTxIsolation(Object txIsolation){ 096 097 mv.visitVarInsn(ALOAD, posTxScope); 098 mv.visitLdcInsn(txIsolation.toString()); 099 mv.visitMethodInsn(INVOKESTATIC, C_TXISOLATION, "valueOf", "(Ljava/lang/String;)L"+C_TXISOLATION+";", false); 100 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setIsolation", "(L"+C_TXISOLATION+";)L"+C_TXSCOPE+";", false); 101 mv.visitInsn(POP); 102 } 103 104 private void setBatch(Object batch){ 105 106 mv.visitVarInsn(ALOAD, posTxScope); 107 mv.visitLdcInsn(batch.toString()); 108 mv.visitMethodInsn(INVOKESTATIC, C_PERSISTBATCH, "valueOf", "(Ljava/lang/String;)L"+C_PERSISTBATCH+";", false); 109 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setBatch", "(L"+C_PERSISTBATCH+";)L"+C_TXSCOPE+";", false); 110 mv.visitInsn(POP); 111 } 112 113 private void setBatchOnCascade(Object batch){ 114 115 mv.visitVarInsn(ALOAD, posTxScope); 116 mv.visitLdcInsn(batch.toString()); 117 mv.visitMethodInsn(INVOKESTATIC, C_PERSISTBATCH, "valueOf", "(Ljava/lang/String;)L"+C_PERSISTBATCH+";", false); 118 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setBatchOnCascade", "(L"+C_PERSISTBATCH+";)L"+C_TXSCOPE+";", false); 119 mv.visitInsn(POP); 120 } 121 122 private void setProfileId(int profileId){ 123 124 mv.visitVarInsn(ALOAD, posTxScope); 125 VisitUtil.visitIntInsn(mv, profileId); 126 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setProfileId", "(I)L"+C_TXSCOPE+";", false); 127 mv.visitInsn(POP); 128 } 129 130 private void setBatchSize(Object batchSize){ 131 132 mv.visitVarInsn(ALOAD, posTxScope); 133 VisitUtil.visitIntInsn(mv, Integer.parseInt(batchSize.toString())); 134 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setBatchSize", "(I)L"+C_TXSCOPE+";", false); 135 mv.visitInsn(POP); 136 } 137 138 private void setGetGeneratedKeys(Object getGeneratedKeys){ 139 boolean getKeys = (Boolean)getGeneratedKeys; 140 if (!getKeys) { 141 mv.visitVarInsn(ALOAD, posTxScope); 142 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setSkipGeneratedKeys", "()L"+C_TXSCOPE+";", false); 143 } 144 } 145 146 private void setReadOnly(Object readOnlyObj){ 147 148 boolean readOnly = (Boolean)readOnlyObj; 149 mv.visitVarInsn(ALOAD, posTxScope); 150 if (readOnly){ 151 mv.visitInsn(ICONST_1); 152 } else { 153 mv.visitInsn(ICONST_0); 154 } 155 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setReadOnly", "(Z)L"+C_TXSCOPE+";", false); 156 } 157 158 private void setFlushOnQuery(Object flushObj){ 159 boolean flushOnQuery = (Boolean)flushObj; 160 if (!flushOnQuery){ 161 mv.visitVarInsn(ALOAD, posTxScope); 162 mv.visitInsn(ICONST_0); 163 mv.visitMethodInsn(INVOKEVIRTUAL, C_TXSCOPE, "setFlushOnQuery", "(Z)L"+C_TXSCOPE+";", false); 164 } 165 } 166 167 /** 168 * Add bytecode to add the noRollbackFor throwable types to the TxScope. 169 */ 170 private void setNoRollbackFor(Object noRollbackFor){ 171 172 ArrayList<?> list = (ArrayList<?>)noRollbackFor; 173 174 for (Object aList : list) { 175 Type throwType = (Type) aList; 176 mv.visitVarInsn(ALOAD, posTxScope); 177 mv.visitLdcInsn(throwType); 178 mv.visitMethodInsn(INVOKEVIRTUAL, txScopeType.getInternalName(), "setNoRollbackFor", "(Ljava/lang/Class;)L" + C_TXSCOPE + ";", false); 179 mv.visitInsn(POP); 180 } 181 } 182 183 /** 184 * Add bytecode to add the rollbackFor throwable types to the TxScope. 185 */ 186 private void setRollbackFor(Object rollbackFor){ 187 188 ArrayList<?> list = (ArrayList<?>)rollbackFor; 189 190 for (Object aList : list) { 191 Type throwType = (Type) aList; 192 mv.visitVarInsn(ALOAD, posTxScope); 193 mv.visitLdcInsn(throwType); 194 mv.visitMethodInsn(INVOKEVIRTUAL, txScopeType.getInternalName(), "setRollbackFor", "(Ljava/lang/Class;)L" + C_TXSCOPE + ";", false); 195 mv.visitInsn(POP); 196 } 197 } 198 199 /** 200 * Return the profileId from the transactional annotation. 201 */ 202 private int annotationProfileId() { 203 Object value = annotationInfo.getValue("profileId"); 204 if (value == null) { 205 return 0; 206 } else { 207 return (int)value; 208 } 209 } 210 211 @Override 212 protected void onMethodEnter() { 213 214 if (!transactional) { 215 return; 216 } 217 218 methodKey = owner.createMethodKey(methodName, methodDesc, annotationProfileId()); 219 posTxScope = newLocal(txScopeType); 220 221 mv.visitTypeInsn(NEW, txScopeType.getInternalName()); 222 mv.visitInsn(DUP); 223 mv.visitMethodInsn(INVOKESPECIAL, txScopeType.getInternalName(), "<init>", "()V", false); 224 mv.visitVarInsn(ASTORE, posTxScope); 225 226 Object txType = annotationInfo.getValue("type"); 227 if (txType != null){ 228 setTxType(txType); 229 } 230 int profileId = methodKey.getProfileId(); 231 if (profileId > 0) { 232 setProfileId(profileId); 233 } 234 235 Object txIsolation = annotationInfo.getValue("isolation"); 236 if (txIsolation != null){ 237 setTxIsolation(txIsolation); 238 } 239 240 Object batch = annotationInfo.getValue("batch"); 241 if (batch != null){ 242 setBatch(batch); 243 } 244 245 Object batchOnCascade = annotationInfo.getValue("batchOnCascade"); 246 if (batchOnCascade != null){ 247 setBatchOnCascade(batchOnCascade); 248 } 249 250 Object batchSize = annotationInfo.getValue("batchSize"); 251 if (batchSize != null){ 252 setBatchSize(batchSize); 253 } 254 255 Object getGeneratedKeys = annotationInfo.getValue("getGeneratedKeys"); 256 if (getGeneratedKeys != null){ 257 setGetGeneratedKeys(getGeneratedKeys); 258 } 259 260 Object readOnly = annotationInfo.getValue("readOnly"); 261 if (readOnly != null){ 262 setReadOnly(readOnly); 263 } 264 265 Object flushOnQuery = annotationInfo.getValue("flushOnQuery"); 266 if (flushOnQuery != null){ 267 setFlushOnQuery(flushOnQuery); 268 } 269 270 271 Object noRollbackFor = annotationInfo.getValue("noRollbackFor"); 272 if (noRollbackFor != null){ 273 setNoRollbackFor(noRollbackFor); 274 } 275 276 Object rollbackFor = annotationInfo.getValue("rollbackFor"); 277 if (rollbackFor != null){ 278 setRollbackFor(rollbackFor); 279 } 280 281 mv.visitVarInsn(ALOAD, posTxScope); 282 mv.visitMethodInsn(INVOKESTATIC, helpScopeTrans.getInternalName(), "enter", "(" 283 + txScopeType.getDescriptor() + ")V", false); 284 } 285 286 287 @Override 288 protected void onFinally(int opcode) { 289 290 if (!transactional) { 291 return; 292 } 293 294 owner.transactionalMethod(methodKey); 295 if (opcode == RETURN) { 296 visitInsn(ACONST_NULL); 297 298 } else if (opcode == ARETURN || opcode == ATHROW) { 299 dup(); 300 301 } else { 302 if (opcode == LRETURN || opcode == DRETURN) { 303 dup2(); 304 } else { 305 dup(); 306 } 307 box(Type.getReturnType(this.methodDesc)); 308 } 309 visitIntInsn(SIPUSH, opcode); 310 visitMethodInsn(INVOKESTATIC, helpScopeTrans.getInternalName(), "exit", "(Ljava/lang/Object;I)V", false); 311 } 312 313}