package org.apache.ode.bpel.wstx;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ode.bpel.engine.MessageImpl;
import org.apache.ode.bpel.iapi.Message;
import org.oasis_open.docs.ws_tx.wscoor._2006._06.CoordinationContextType;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.arjuna.mw.wst.TxContext;
import com.arjuna.mw.wst11.BusinessActivityManager;
import com.arjuna.mw.wst11.TransactionManager;
import com.arjuna.mw.wst11.UserBusinessActivity;
import com.arjuna.mw.wst11.UserTransaction;
import com.arjuna.mw.wst11.common.CoordinationContextHelper;
import com.arjuna.mwlabs.wst11.ba.context.TxContextImple;
import com.arjuna.webservices11.wscoor.CoordinationConstants;
import com.arjuna.wst.SystemException;
import com.arjuna.wst.TransactionRolledBackException;
import com.arjuna.wst.UnknownTransactionException;
import com.arjuna.wst.WrongStateException;

/**
 * 
 * TODO: consider AtomicOutcome and MixedOutcome
 * 
 * @author ibek
 *
 */
public class BusinessActivity implements WebServiceTransaction {

    private static final Log __log = LogFactory.getLog(BusinessActivity.class);

    protected UserBusinessActivity _uba;
    protected TxContext _txcontext;
    protected boolean _active;
    protected WebServiceTransactionType _type;
    protected boolean _subordinate;

    public BusinessActivity(WebServiceTransactionType type) {
        _type = type;
        _active = false;
        _subordinate = false;
    }

    /**
     * Begin of transaction must be performed with mutual exclusion in one thread
     * because the registration service cannot begin more transactions concurrently.
     */
    private static synchronized void begin(UserBusinessActivity uba) throws WrongStateException, SystemException{
        uba.begin();
    }

    public void begin(Message bpelRequest) throws WrongStateException, SystemException {
		MessageImpl req = (MessageImpl)bpelRequest;
		_subordinate = false;
        if(req._dao.getHeader() != null){
            try {
                NodeList cc = req._dao.getHeader().getElementsByTagNameNS(CoordinationConstants.WSCOOR_NAMESPACE, 
                        CoordinationConstants.WSCOOR_ELEMENT_COORDINATION_CONTEXT);
                if (cc.getLength() > 0){
                    CoordinationContextType cct = CoordinationContextHelper.deserialise((Element)cc.item(0).getFirstChild());
                    if (cct != null) {
                        TxContext ctx = new TxContextImple(cct);
                        BusinessActivityManager.getBusinessActivityManager().resume(ctx);
                        _subordinate = true;
                    }
                }
            } catch (Exception e) {
                __log.warn("Wrong coordination context. The transaction won't be subordinated.");
            }
        }

        _uba = UserBusinessActivity.getUserBusinessActivity();
				if (_subordinate && _uba != null) {
            _uba = UserBusinessActivity.getUserBusinessActivity().getUserSubordinateBusinessActivity();
        }

        if (_uba == null)
            throw new SystemException(
                    "Distributed transaction has not been created. Check that JBoss XTS is runnning.");
        try {
            begin(_uba);
        } catch (WrongStateException wse) {
            BusinessActivityManager.getBusinessActivityManager().suspend(); // previous transaction will be resumed by another instance
            _uba = UserBusinessActivity.getUserBusinessActivity();
            begin(_uba); // we try again to create new transaction
        }
        _txcontext = BusinessActivityManager.getBusinessActivityManager().currentTransaction();
        _active = true;
    }

    public void commit() throws SecurityException, UnknownTransactionException, SystemException,
            WrongStateException {
        _active = false;
        try {
            resume();
            _uba.close();
        } catch (TransactionRolledBackException e) {
            __log.info("Web service transaction was aborted");
        } finally {
            _uba = null;
            _txcontext = null;
        }
    }
    
    public void complete() throws UnknownTransactionException, SystemException, WrongStateException {
        try {
            resume();
            _uba.complete();
        } catch (WrongStateException ex) {
            __log.warn("Business Activity Transaction WrongStateException for completion.");
        }
    }

    public boolean isActive() {
        return _uba != null && _active;
    }
    
    public boolean isSubordinate() {
        return _subordinate;
    }

    public void rollback() throws SecurityException, UnknownTransactionException, SystemException,
            WrongStateException {
        _active = false;
        try{
            resume();
            _uba.cancel();
        } finally {
            _uba = null;
            _txcontext = null;
        }
    }

    public String getTransactionIdentifier() {
        return _uba.transactionIdentifier();
    }
    
    public WebServiceTransactionType getType() {
        return _type;
    }

    public void resume() throws UnknownTransactionException, SystemException {
        if (!_txcontext.equals(BusinessActivityManager.getBusinessActivityManager().currentTransaction())) {
            BusinessActivityManager.getBusinessActivityManager().resume(_txcontext);
            _uba = UserBusinessActivity.getUserBusinessActivity();
        }
    }

    public void suspend() throws SystemException {
        _txcontext = BusinessActivityManager.getBusinessActivityManager().suspend();
    }

    public Element putCoordinationContext(Element headerElement)
            throws UnknownTransactionException, SystemException {
        resume();
        final TxContextImple txContext = (TxContextImple) BusinessActivityManager.getBusinessActivityManager().currentTransaction();
        CoordinationContextType ctx = txContext.context().getCoordinationContext();
        try {
            Document doc = headerElement.getOwnerDocument();
            Element coord = doc.createElementNS(CoordinationConstants.WSCOOR_NAMESPACE,
                    CoordinationConstants.WSCOOR_ELEMENT_COORDINATION_CONTEXT);
            headerElement.appendChild(coord);
            CoordinationContextHelper.serialise(ctx, headerElement);
            
            Element parent = doc.createElementNS(CoordinationConstants.WSCOOR_NAMESPACE,
                    CoordinationConstants.WSCOOR_ELEMENT_COORDINATION_CONTEXT);
            Node tmp = headerElement.getElementsByTagNameNS(CoordinationConstants.WSCOOR_NAMESPACE, CoordinationConstants.WSCOOR_ELEMENT_COORDINATION_CONTEXT).item(0);
            parent.appendChild(tmp.cloneNode(true));
            
            headerElement.replaceChild(parent, tmp);
        } catch (Exception e) {
            e.printStackTrace();
            throw new SystemException("Coordination context has not been added to the header.");
        }
        return headerElement;
    }

}
