28 June 2012

Using Dynamic Proxies to centralize Transaction Management (2)

I've talked about Using Dynamic Proxies to centralize JPA code In an earlier time, and here's a refined version of the Proxy class:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.logging.Logger;

import org.hibernate.HibernateException;
import org.hibernate.Session;

import org.daz.DBException;
import org.daz.SetRollbackException;
import org.daz.HibernateUtil;

/**
 * http://stackoverflow.com/questions/2587702
 * 
 * @author mohammad_abdullah
 */
public class ServiceProxy implements InvocationHandler {

    private Object object;
    private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
    private static final String SESSION_FIELD = "session";

    private ServiceProxy(Object object) {
        this.object = object;
    }

    public static Object newInstance(Object object) {
        return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new ServiceProxy(object));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        Object result = null;
        Session session = null;
        boolean joined = false;
        try {
            if (Modifier.isPublic(method.getModifiers())) {

                session = HibernateUtil.getSessionFactory().getCurrentSession();

                Field sessionField = object.getClass().getSuperclass().getDeclaredField(SESSION_FIELD);
                if (sessionField == null)
                    throw new RuntimeException("Service Implementation should have field named: \"" + SESSION_FIELD + "\".");
                sessionField.setAccessible(true);
                sessionField.set(object, session);

                if (session.getTransaction().isActive()) {
                    joined = true;
                    logger.info("Using Already Active transaction");
                } else {
                    logger.info("Transaction Began");
                    session.beginTransaction();
                }
                result = method.invoke(object, args);

                if (!joined) {
                    session.getTransaction().commit();
                    logger.info("Transaction Commited");
                }
            } else {
                result = method.invoke(object, args);
            }

            return result;

        } catch (InvocationTargetException _ex) {
            Throwable cause = _ex.getCause();
            logger.severe("Caller Exception: " + cause);

            if (!joined && session != null && session.getTransaction().isActive())
                session.getTransaction().rollback();

            if (cause instanceof HibernateException) {
                logger.severe("Hibernate Error. Rollbacked Back.");
                throw new DBException(cause.getCause().getMessage());

            /*SetRollbackException is a user-defined exception that can be thrown from Business method to mark transaction for rollback */
            } else if (cause instanceof SetRollbackException) {        
                logger.severe("Transaction marked for Rollback. Rollbacked Back.");
                return result;

            } else {
                logger.severe("Error in Business Method : " + method + ". Rollbacked Back.");
                throw cause;
            }
        } catch (Exception ex) {
            logger.severe("Error in Proxy code :" + ex);

            if (!joined && session != null && session.getTransaction().isActive())
                session.getTransaction().rollback();

            if (ex instanceof HibernateException)
                throw new DBException(ex.getCause().getMessage());

            throw ex;
        }
    }
}

No comments: