package com.ciant.techgallery.transaction; import com.googlecode.objectify.ObjectifyService; import com.googlecode.objectify.Work; import com.ciant.techgallery.transaction.idempotency.IdempotencyHandler; import com.ciant.techgallery.transaction.idempotency.IdempotencyHandlerFactory; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.logging.Level; import java.util.logging.Logger; /** * Transactional interceptor. Checks if the method which is being called should run in a transaction * or not. * * @author bcarneiro * */ public class TransactionalInvocationHandler implements InvocationHandler { private static Logger log = Logger.getLogger(TransactionalInvocationHandler.class.getName()); private Object implementation; /** * @param implementation - Instance that will be wrapped and called in a transaction or not * depending Transactional annotation. */ public TransactionalInvocationHandler(Class<?> implementation) { try { Constructor<?> constructor = implementation.getDeclaredConstructor(); constructor.setAccessible(true); this.implementation = constructor.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } @SuppressWarnings({"unchecked", "rawtypes"}) @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { try { final Method implementationMethod = implementation.getClass().getMethod(method.getName(), method.getParameterTypes()); final Transactional transactional = implementationMethod.getAnnotation(Transactional.class); if (transactional != null) { final IdempotencyHandler idempotencyHandler = IdempotencyHandlerFactory.createHandlerAnnotationBased(transactional); return ObjectifyService.ofy().execute(transactional.type(), new Work() { @Override public Object run() { if (idempotencyHandler.shouldTransactionProceed(proxy, method, args)) { Object result = invokeMethod(args, implementationMethod); idempotencyHandler.setReturn(result); } return idempotencyHandler.getReturn(); } }); } else { return invokeMethod(args, implementationMethod); } } catch (RuntimeException err) { throw getRootCause(err, 5); } } private Throwable getRootCause(Throwable err, int level) { return level > 0 && err.getCause() != null ? getRootCause(err.getCause(), level - 1) : err; } private Object invokeMethod(final Object[] args, final Method implementationMethod) { try { return implementationMethod.invoke(implementation, args); } catch (Exception e) { log.log(Level.SEVERE, "Error in a transactional method", e); if (e.getCause() != null) { e = (Exception) e.getCause(); } if (e instanceof RuntimeException) { throw (RuntimeException) e; } throw new RuntimeException(e); } } }