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

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

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

import org.apache.commons.lang.Validate;
import org.springframework.transaction.support.TransactionSynchronization;

import gov.va.med.fw.cache.TriggerEventCacheManager;
import gov.va.med.fw.service.AbstractComponent;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.service.trigger.TriggerEvent;
import gov.va.med.fw.service.trigger.TriggerRouter;

/**
 * TransactionSynchronization implementation to ensure that TriggerEvent objects
 * are not routed (ie, published for consumption) until the Transaction they
 * were created in is commited. TriggerEvent's are discarded in case of
 * rollback.
 *
 * NOTE: This implementation is stateful in cases of suspend/resume.
 *
 * Created Nov 2, 2005 1:57:49 PM
 *
 * @author DNS   BOHMEG
 */
public class TransactionSynchronizationForEvents extends AbstractComponent
        implements TransactionSynchronization {
    private TriggerRouter triggerRouter;

    private TriggerEventCacheManager cacheManager;

    private boolean supportsDistributedTransactions;

    private Set triggerEvents;

    private int ROUTE_NOW = TransactionSynchronization.STATUS_COMMITTED;

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.transaction.support.TransactionSynchronization#suspend()
     */
    public void suspend() {
        // see if any events present, if so, take with me
        if (cacheManager.hasTriggerEvents()) {
            triggerEvents = cacheManager.removeTriggerEvents();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.transaction.support.TransactionSynchronization#resume()
     */
    public void resume() {
        if (triggerEvents != null) {
            cacheManager.storeTriggerEvents(triggerEvents);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit(boolean)
     */
    public void beforeCommit(boolean readOnly) {
        /** If two-phase commit is enabled, then it is ok to route before commit since
         * the actual JMS messages will not be consumed until wrapping JTA Transaction commits.
         */
        if(supportsDistributedTransactions) {
            routeEvents(cacheManager.removeTriggerEvents(), ROUTE_NOW);
        }
    }

    /**
     * @see org.springframework.transaction.support.TransactionSynchronization#afterCommit()
     */
    public void afterCommit() {
        // TODO Auto-generated method stub
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.transaction.support.TransactionSynchronization#beforeCompletion()
     */
    public void beforeCompletion() {
        // TODO Auto-generated method stub

    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.transaction.support.TransactionSynchronization#afterCompletion(int)
     */
    public void afterCompletion(int status) {
    	/* Irregardless of Transaction status (eg, perhaps beforeCommit was never even called), we
    	 * need to remove "cached" TriggerEvents.  Depending on configuration, for example, they
    	 * could be cached on the ThreadLocal.
    	 */
    	Set events = cacheManager.removeTriggerEvents();

        if(!supportsDistributedTransactions)
            routeEvents(events, status);
    }

    private void routeEvents(Set events, int transactionStatus) {
        if(transactionStatus == ROUTE_NOW) {
            Iterator itr = events != null ? events.iterator() : null;
            TriggerEvent event = null;
            try {
                while (itr != null && itr.hasNext()) {
                    event = (TriggerEvent) itr.next();
                    triggerRouter.processTriggerEvent(event);
                }
            } catch (ServiceException e) {
                RuntimeException wrapped = new IllegalStateException(
                        "Was unable to route TriggerEvents");
                wrapped.initCause(e);
                throw wrapped;
            }
        } else {
        	// do nothing
        }
    }

    /**
     * @return Returns the triggerRouter.
     */
    public TriggerRouter getTriggerRouter() {
        return triggerRouter;
    }

    /**
     * @param triggerRouter
     *            The triggerRouter to set.
     */
    public void setTriggerRouter(TriggerRouter triggerRouter) {
        this.triggerRouter = triggerRouter;
    }

    public void afterPropertiesSet() throws Exception {
        super.afterPropertiesSet();
        Validate.notNull(triggerRouter);
        Validate.notNull(cacheManager);
    }

    /**
     * @return Returns the cacheManager.
     */
    public TriggerEventCacheManager getCacheManager() {
        return cacheManager;
    }

    /**
     * @param cacheManager
     *            The cacheManager to set.
     */
    public void setCacheManager(TriggerEventCacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    public boolean isSupportsDistributedTransactions() {
        return supportsDistributedTransactions;
    }

    public void setSupportsDistributedTransactions(
            boolean supportsDistributedTransactions) {
        this.supportsDistributedTransactions = supportsDistributedTransactions;
    }
}
