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

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

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.Validate;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;

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

/**
 * An AOP interceptor that is used as a dynamic postInterceptor on
 * SynchronizableTransactionProxyFactoryBean. It is responsible for performing
 * any tasks upon Transaction begin (eg, create and cache global Transaction
 * timestamp), as well as registering any configured TransactionSynchronization
 * implementations.
 * 
 * Created Nov 2, 2005 1:45:23 PM
 * 
 * @author DNS   BOHMEG
 * 
 * @see gov.va.med.fw.service.transaction.TransactionSynchronizationInitializer
 */
public class TransactionSynchronizationInitializer extends AbstractComponent implements
		MethodInterceptor {
	private Set transactionSynchronizations;
	private TransactionTimestampManager timestampManager;

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept
	 * .MethodInvocation)
	 */
	public Object invoke(MethodInvocation invocation) throws Throwable {
		if (TransactionSynchronizationManager.isSynchronizationActive()) {

			List alreadyRegisteredSyncs = TransactionSynchronizationManager.getSynchronizations();

			Iterator itrSyncsToAdd = transactionSynchronizations != null ? transactionSynchronizations
					.iterator()
					: null;
			String syncName = null;
			Set justAddedSyncs = new HashSet();
			boolean tsAdded = false;
			while (itrSyncsToAdd != null && itrSyncsToAdd.hasNext()) {
				syncName = (String) itrSyncsToAdd.next();
				// before adding this, make sure it is not already added
				Iterator itr = alreadyRegisteredSyncs != null ? alreadyRegisteredSyncs.iterator()
						: null;
				Object sync = null;
				boolean alreadyAdded = false;
				while (itr != null && itr.hasNext()) {
					sync = itr.next();
					if (sync instanceof AbstractComponent) {
						if (((AbstractComponent) sync).getBeanName().equals(syncName)) {
							alreadyAdded = true;
							break; // already added
						}
					}
				}
				if (!alreadyAdded && !justAddedSyncs.contains(syncName)) {
					// System.out.println("binding TransactionSynchronization named: "
					// + syncName);
					justAddedSyncs.add(syncName);
					// lazily get bean to ensure prototype (ie, different sync
					// per invocation)
					TransactionSynchronization syncInstance = (TransactionSynchronization) getApplicationContext()
							.getBean(syncName);
					TransactionSynchronizationManager.registerSynchronization(syncInstance);
					if (!tsAdded) {
						timestampManager.intializeTransactionTimestamp(true);
						tsAdded = true;
					}
				}
			}
		}

		return invocation.proceed();
	}

	public void afterPropertiesSet() {
		Validate.notNull(transactionSynchronizations,
				"can not use this class without transactionSynchronizations");
		Validate.notNull(timestampManager);
	}

	/**
	 * @return Returns the transactionSynchronizations.
	 */
	public Set getTransactionSynchronizations() {
		return transactionSynchronizations;
	}

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

	/**
	 * @return Returns the timestampManager.
	 */
	public TransactionTimestampManager getTimestampManager() {
		return timestampManager;
	}

	/**
	 * @param timestampManager
	 *            The timestampManager to set.
	 */
	public void setTimestampManager(TransactionTimestampManager timestampManager) {
		this.timestampManager = timestampManager;
	}
}
