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}