View Javadoc
1   package com.atlassian.activeobjects.tx;
2   
3   import com.atlassian.activeobjects.external.ActiveObjects;
4   import com.atlassian.sal.api.transaction.TransactionCallback;
5   
6   import java.lang.annotation.Annotation;
7   import java.lang.reflect.AnnotatedElement;
8   import java.lang.reflect.InvocationHandler;
9   import java.lang.reflect.InvocationTargetException;
10  import java.lang.reflect.Method;
11  import java.lang.reflect.Proxy;
12  
13  import static com.google.common.base.Preconditions.checkNotNull;
14  
15  /**
16   * The proxy that takes care of wrapping annotated methods within a transaction.
17   */
18  public final class TransactionalProxy implements InvocationHandler {
19      private static final Class<? extends Annotation> ANNOTATION_CLASS = Transactional.class;
20  
21      private final ActiveObjects ao;
22      private final Object obj;
23  
24      public TransactionalProxy(ActiveObjects ao, Object obj) {
25          this.ao = ao;
26          this.obj = obj;
27      }
28  
29      public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
30          if (isAnnotated(method)) {
31              return invokeInTransaction(method, args);
32          } else {
33              return invoke(method, args);
34          }
35      }
36  
37      private Object invokeInTransaction(final Method method, final Object[] args) throws Throwable {
38          try {
39              return executeInTransaction(method, args);
40          } catch (TransactionalException e) {
41              throw e.getThrowable();
42          }
43      }
44  
45      private Object executeInTransaction(final Method method, final Object[] args) {
46          return ao.executeInTransaction(new TransactionCallback<Object>() {
47              public Object doInTransaction() {
48                  try {
49                      return invoke(method, args);
50                  } catch (IllegalAccessException e) {
51                      throw new TransactionalException(e);
52                  } catch (InvocationTargetException e) {
53                      throw new TransactionalException(e);
54                  }
55              }
56          });
57      }
58  
59      private Object invoke(Method method, Object[] args) throws IllegalAccessException, InvocationTargetException {
60          return method.invoke(obj, args);
61      }
62  
63      /**
64       * Makes the given instance object transactional. It will do so by proxying the object, so one can no longer
65       * reference the original object implementation after calling this method.
66       *
67       * @param ao the {@link com.atlassian.activeobjects.external.ActiveObjects} service to use for transaction management.
68       * @param o  the object to make transactional.
69       * @return a transactional proxy of the object passed as a parameter.
70       */
71      public static Object transactional(ActiveObjects ao, Object o) {
72          checkNotNull(o);
73          final Class c = o.getClass();
74          return Proxy.newProxyInstance(c.getClassLoader(), c.getInterfaces(), new TransactionalProxy(ao, o));
75      }
76  
77      static boolean isAnnotated(Method method) {
78          return method != null && (isAnnotationPresent(method) || isAnnotationPresent(method.getDeclaringClass()));
79      }
80  
81      /**
82       * Tells whether the given class is annotated as being transactional. I.e with annotation defined at {@link #ANNOTATION_CLASS}.
83       *
84       * @param c the class to scan for annotations
85       * @return {@code true} if the class is annotated with the defined annotation
86       */
87      public static boolean isAnnotated(Class c) {
88          if (c != null) {
89              if (c.isInterface()) {
90                  if (isAnnotationPresent(c)) {
91                      return true;
92                  }
93                  for (Method method : c.getMethods()) {
94                      if (isAnnotated(method)) {
95                          return true;
96                      }
97                  }
98              }
99  
100             for (Class ifce : c.getInterfaces()) {
101                 if (isAnnotated(ifce)) {
102                     return true;
103                 }
104             }
105         }
106         return false;
107     }
108 
109     private static boolean isAnnotationPresent(AnnotatedElement e) {
110         return e.isAnnotationPresent(ANNOTATION_CLASS);
111     }
112 
113     private static final class TransactionalException extends RuntimeException {
114         public TransactionalException(Throwable cause) {
115             super(cause);
116         }
117 
118         public Throwable getThrowable() {
119             final Throwable cause = getCause();
120             if (cause instanceof InvocationTargetException) {
121                 return cause.getCause();
122             } else {
123                 return cause;
124             }
125         }
126     }
127 }