/********************************************************************
 * Copyright © 2006 VHA. All rights reserved
 ********************************************************************/

package gov.va.med.fw.service.transaction;

import java.util.Iterator;
import java.util.Set;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import gov.va.med.fw.service.AbstractComponent;

/**
 * Makes determination to ensure infrastructure rollback of Transactions,
 * allowing for a bypass listing
 * 
 * <p>
 * Will rollback only if current caller started the Transaction AND current
 * exception is not in bypass set.
 * 
 * <p>
 * Will currently overrule any declarative Transaction settings in Spring (TODO:
 * determine the bypass listing from Spring declaration rather than a separate
 * bypassRollbackSet injected here)
 * 
 * Created May 19, 2006 10:21:45 AM
 * 
 * @author DNS   BOHMEG
 */
public class RollbackAdvice extends AbstractComponent implements MethodInterceptor {
	private Set bypassRollbackSet;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept
	 * .MethodInvocation)
	 */
	public Object invoke(MethodInvocation arg0) throws Throwable {
		Object result = null;

		// see if there is an active Transaction, if not, do nothing special
		if (!TransactionSynchronizationManager.isActualTransactionActive())
			result = arg0.proceed();
		else {
			try {
				result = arg0.proceed();
			} catch (Throwable t) {
				// rollback only if this MethodInvocation started the
				// Transaction
				String currentCallerClass = arg0.getMethod().getDeclaringClass().getName();
				String currentCallerMethod = arg0.getMethod().getName();

				// Form a transaction name using both method and class name
				StringBuilder name = new StringBuilder(currentCallerClass);
				name.append(".").append(currentCallerMethod);

				/*
				 * assumes that current Transaction name is a combination of
				 * class.method (as specified in
				 * org.springframework.transaction.
				 * TransactionDefinition.getName() documentation)
				 */
				String txName = TransactionSynchronizationManager.getCurrentTransactionName();

				if (StringUtils.isBlank(txName)) {
					if (logger.isErrorEnabled()) {
						logger
								.error("TransactionSynchronizationManager.isActualTransactionActive() states true, but "
										+ "TransactionSynchronizationManager.getCurrentTransactionName() returns null.  Current caller is: "
										+ name);
					}
				}
				// Using the class.method to ensure that it will never be NULL
				if (name.toString().equalsIgnoreCase(txName)) {

					// ok, the current caller started Transaction.....now make
					// sure not in bypass list
					if (!shouldBypassRollback(t))
						rollback(t, txName);
				}

				// if get here, just throw original Throwable
				throw t;
			}
		}
		return result;
	}

	private boolean shouldBypassRollback(Throwable t) {
		// TODO: enhance this to get info from Spring bean definition

		if (bypassRollbackSet == null)
			return false;

		Iterator itr = bypassRollbackSet.iterator();
		String exceptionClassName = null;
		Class loadedExceptionClass = null;
		try {
			while (itr.hasNext()) {
				exceptionClassName = (String) itr.next();
				loadedExceptionClass = Class.forName(exceptionClassName);
				if (loadedExceptionClass.isAssignableFrom(t.getClass()))
					return true;
			}
		} catch (ClassNotFoundException e) {
			logger.error(
					"Unable to load a class from its name in RollbackAdvice.bypassRollbackSet: "
							+ exceptionClassName, e);
		}
		return false;
	}

	private void rollback(Throwable t, String txName) throws Throwable {
		// this has negative impact of changes required on callers (or a
		// preInterceptor to unwrap after TransactionInterceptor)
		// throw new RollbackApplicationException(t);

		if (logger.isErrorEnabled())
			logger.error("Rolling back application transaction named: " + txName);
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}

	/**
	 * @return Returns the bypassRollbackSet.
	 */
	public Set getBypassRollbackSet() {
		return bypassRollbackSet;
	}

	/**
	 * @param bypassRollbackSet
	 *            The bypassRollbackSet to set.
	 */
	public void setBypassRollbackSet(Set bypassRollbackSet) {
		this.bypassRollbackSet = bypassRollbackSet;
	}
}
