/********************************************************************
 * 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(syncInstance instanceof TransactionSynchronizationForCacheManager) {
						// annotate the Thread for later interrogation
						TransactionSynchronizationManager.bindResource(
								TransactionSynchronizationForCacheManager.CACHE_AUTOCLEANUP_VIA_TRANSACTION_SYNCHRONIZATION, Boolean.TRUE.toString());
					}
					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;
	}
}
