001package io.ebean.enhance.transactional;
002
003import io.ebean.enhance.asm.AnnotationVisitor;
004import io.ebean.enhance.asm.ClassVisitor;
005import io.ebean.enhance.asm.MethodVisitor;
006import io.ebean.enhance.asm.Opcodes;
007import io.ebean.enhance.common.AlreadyEnhancedException;
008import io.ebean.enhance.common.AnnotationInfo;
009import io.ebean.enhance.common.AnnotationInfoVisitor;
010import io.ebean.enhance.common.ClassMeta;
011import io.ebean.enhance.common.EnhanceConstants;
012import io.ebean.enhance.common.EnhanceContext;
013
014import java.util.ArrayList;
015import java.util.logging.Level;
016import java.util.logging.Logger;
017
018/**
019 * ClassAdapter used to add transactional support.
020 */
021public class ClassAdapterTransactional extends ClassVisitor {
022
023        private static final Logger logger = Logger.getLogger(ClassAdapterTransactional.class.getName());
024
025        private final ArrayList<String> transactionalMethods = new ArrayList<>();
026
027        private final EnhanceContext enhanceContext;
028
029        private final ClassLoader classLoader;
030
031        private ArrayList<ClassMeta> transactionalInterfaces = new ArrayList<>();
032
033        /**
034         * Class level annotation information.
035         */
036        private AnnotationInfo classAnnotationInfo;
037
038        private String className;
039
040        public ClassAdapterTransactional(ClassVisitor cv, ClassLoader classLoader, EnhanceContext context) {
041                super(Opcodes.ASM6, cv);
042                this.classLoader = classLoader;
043                this.enhanceContext = context;
044        }
045
046        public String className() {
047                return className;
048        }
049
050        public boolean isLog(int level) {
051                return enhanceContext.isLog(level);
052        }
053
054        public void log(String msg) {
055                enhanceContext.log(className, msg);
056        }
057
058  public AnnotationInfo getClassAnnotationInfo() {
059    return classAnnotationInfo;
060  }
061  
062        /**
063         * Returns Transactional information from a matching interface method.
064         * <p>
065         * Returns null if no matching (transactional) interface method was found.
066         * </p>
067         * 
068         * @param methodName
069         *            The method name
070         * @param methodDesc
071         *            The method description
072         */
073        public AnnotationInfo getInterfaceTransactionalInfo(String methodName, String methodDesc) {
074
075                AnnotationInfo interfaceAnnotationInfo = null;
076
077                for (int i = 0; i < transactionalInterfaces.size(); i++) {
078                        ClassMeta interfaceMeta = transactionalInterfaces.get(i);
079                        AnnotationInfo ai = interfaceMeta.getInterfaceTransactionalInfo(methodName, methodDesc);
080                        if (ai != null) {
081                                if (interfaceAnnotationInfo != null) {
082                                        String msg = "Error in [" + className + "] searching the transactional interfaces ["
083                                                        + transactionalInterfaces + "] found more than one match for the transactional method:"
084                                                        + methodName + " " + methodDesc;
085
086                                        logger.log(Level.SEVERE, msg);
087
088                                } else {
089                                        interfaceAnnotationInfo = ai;
090                                        if (isLog(2)) {
091                                                log("inherit transactional from interface [" + interfaceMeta + "] method[" + methodName + " "
092                                                                + methodDesc + "]");
093                                        }
094                                }
095                        }
096                }
097
098                return interfaceAnnotationInfo;
099        }
100
101        /**
102         * Visit the class with interfaces.
103         */
104        @Override
105        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
106
107                className = name;
108
109                // Note: interfaces can be an empty array but not null
110                int n = 1 + interfaces.length;
111                String[] newInterfaces = new String[n];
112                for (int i = 0; i < interfaces.length; i++) {
113                        newInterfaces[i] = interfaces[i];
114                        if (newInterfaces[i].equals(EnhanceConstants.C_ENHANCEDTRANSACTIONAL)) {
115                                throw new AlreadyEnhancedException(name);
116                        }
117                        ClassMeta interfaceMeta = enhanceContext.getInterfaceMeta(newInterfaces[i], classLoader);
118                        if (interfaceMeta != null && interfaceMeta.isTransactional()) {
119                                // the interface was transactional. We gather its information
120                                // because our methods inherit that transactional configuration
121                                transactionalInterfaces.add(interfaceMeta);
122                                
123                                if (isLog(6)) {
124                                        log(" implements tranactional interface " + interfaceMeta.getDescription());
125                                }
126                        }
127                }
128
129                // Add the EnhancedTransactional interface
130                newInterfaces[newInterfaces.length - 1] = EnhanceConstants.C_ENHANCEDTRANSACTIONAL;
131
132                super.visit(version, access, name, signature, superName, newInterfaces);
133        }
134
135        /**
136         * Visit class level annotations.
137         */
138        @Override
139        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
140
141                AnnotationVisitor av = super.visitAnnotation(desc, visible);
142
143                if (desc.equals(EnhanceConstants.AVAJE_TRANSACTIONAL_ANNOTATION)) {
144                        // we have class level Transactional annotation
145                        // which will act as default for all methods in this class
146                        classAnnotationInfo = new AnnotationInfo(null);
147                        return new AnnotationInfoVisitor(null, classAnnotationInfo, av);
148
149                } else {
150                        return av;
151                }
152        }
153
154        /**
155         * Visit the methods specifically looking for method level transactional
156         * annotations.
157         */
158        @Override
159        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
160
161                MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
162                if (name.equals("<init>") || name.equals("<clinit>")) {
163                        // not enhancing constructors at the moment
164                        return mv;
165                }
166                return new ScopeTransAdapter(this, mv, access, name, desc);
167        }
168
169        @Override
170        public void visitEnd() {
171                if (isLog(2)) {
172                        log("methods:" + transactionalMethods);
173                }
174                super.visitEnd();
175        }
176
177        void transactionalMethod(TransactionalMethodKey methodKey) {
178
179                transactionalMethods.add(methodKey.getMethodName());
180                if (isLog(3)) {
181                        log("method - " + methodKey);
182                }
183        }
184
185        /**
186         * Create and return the TransactionalMethodKey.
187         *
188         * Takes into account the profiling mode (as per manifest) and explicit profileId.
189         */
190        public TransactionalMethodKey createMethodKey(String methodName, String methodDesc, int profId) {
191                return enhanceContext.createMethodKey(className, methodName, methodDesc, profId);
192        }
193}