/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2000-2010 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.messaging.jms.ra;

import javax.jms.JMSException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.XAException;
import javax.transaction.xa.Xid;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.HashMap;

import com.sun.messaging.jmq.util.XidImpl;
import com.sun.messaging.jmq.jmsclient.Debug;
import com.sun.messaging.jmq.jmsservice.JMSService;
import com.sun.messaging.jmq.jmsservice.JMSServiceReply;
import com.sun.messaging.jmq.jmsservice.JMSServiceException;
import com.sun.messaging.jms.ra.util.DirectXAResourceMap;

/**
 *  DirectXAResource encapsulates XAResource functionality for DIRECT mode.<p>
 * 
 *  The XAResource interface is a Java mapping of the industry standard
 *  XA interface based on the X/Open CAE Specification (Distributed
 *  Transaction Processing: The XA Specification).
 * 
 *  <p>The XA interface defines the contract between a Resource Manager
 *  and a Transaction Manager in a distributed transaction processing
 *  (DTP) environment. A JDBC driver or a JMS provider implements
 *  this interface to support the association between a global transaction
 *  and a database or message service connection.
 * 
 *  <p>The XAResource interface can be supported by any transactional
 *  resource that is intended to be used by application programs in an
 *  environment where transactions are controlled by an external
 *  transaction manager. An example of such a resource is a database
 *  management system. An application may access data through multiple
 *  database connections. Each database connection is enlisted with
 *  the transaction manager as a transactional resource. The transaction
 *  manager obtains an XAResource for each connection participating
 *  in a global transaction. The transaction manager uses the
 *  <code>start</code> method
 *  to associate the global transaction with the resource, and it uses the
 *  <code>end</code> method to disassociate the transaction from
 *  the resource. The resource
 *  manager is responsible for associating the global transaction to all
 *  work performed on its data between the start and end method invocations.
 * 
 *  <p>At transaction commit time, the resource managers are informed by
 *  the transaction manager to prepare, commit, or rollback a transaction
 *  according to the two-phase commit protocol.</p>
 */
public class DirectXAResource
implements XAResource {
	
    /** The jmsservice that is assciated with this DirectXAResource */
    private JMSService jmsservice;

    /** The DirectConnection that is associated with this DirectXAResource */
    private DirectConnection dc;

    /** The connectionId (from DirectConnection) for this DirectXAResource */
    private long connectionId;

    /** The default transactionId and xid for this XAResource */
    private long mTransactionId = 0L;
    private XidImpl mXid = null;
    private int id = 0;

    /** Indicates whether this DirectXAResource has been enlisted or not */
    private boolean isEnlisted = false;

    /** Indicates whether this DirectXAResource is used by an MDB */
    private boolean usedByMDB = false;

    /** The xid / transactionId mapping table */
    private HashMap<String, Long> transactionIds = null;
    /**
     *  Logging
     */
    private static transient final String _className = "com.sun.messaging.jms.ra.DirectXAResource";
    private static transient final String _lgrNameOutboundConnection = "javax.resourceadapter.mqjmsra.outbound.connection";
    private static transient final String _lgrNameJMSXAResource = "javax.resourceadapter.mqjmsra.xa";
    private static transient final Logger _loggerOC = Logger.getLogger(_lgrNameOutboundConnection);
    private static transient final Logger _loggerJX = Logger.getLogger(_lgrNameJMSXAResource);
    private static transient final String _lgrName = "com.sun.messaging.jms.ra.DirectXAResource";
    private static transient final Logger _logger = Logger.getLogger(_lgrName);
    private static transient final String _lgrMIDPrefix = "MQJMSRA_DXA";
    private static transient final String _lgrMID_EET = _lgrMIDPrefix+"1001: ";
    private static transient final String _lgrMID_INF = _lgrMIDPrefix+"1101: ";
    private static transient final String _lgrMID_WRN = _lgrMIDPrefix+"2001: ";
    private static transient final String _lgrMID_ERR = _lgrMIDPrefix+"3001: ";
    private static transient final String _lgrMID_EXC = _lgrMIDPrefix+"4001: ";

    private static int idCounter = 0;

    /** For optimized logging */
    protected static int _logLevel;
    protected static boolean _logFINE = false;
    
    /** 
     * Possible states of this XAResource
     */
    public static final int CREATED    = 0; // after first creation, or after commit() or rollback()
    public static final int STARTED    = 1; // after start() called
    public static final int FAILED     = 2; // after end(fail) called
    public static final int INCOMPLETE = 3; // after end(suspend) called
    public static final int COMPLETE   = 4; // after end (success) called
    public static final int PREPARED   = 5; // after prepare() called
    
    /**
     * State of this XAresource
     */
    private int resourceState = CREATED;
        
	static {                                                                        
//        _loggerOC = Logger.getLogger(_lgrNameOutboundConnection);
//        _loggerJX = Logger.getLogger(_lgrNameJMSXAResource);
        java.util.logging.Level _level = _loggerJX.getLevel();
        if (_level != null) {
            _logLevel = _level.intValue();
            if (_logLevel <= java.util.logging.Level.FINE.intValue()){
                _logFINE = true;
            }
        }
    }

    /**
     * Creates a new instance of DirectXAResource
     */
    public DirectXAResource(DirectConnection dc,
            JMSService jmsservice, long connectionId) {
        Object params[] = new Object[3];
        params[0] = dc;
        params[1] = jmsservice;
        params[2] = connectionId;
        _loggerOC.entering(_className, "constructor()", params);        
        this.dc = dc;
        this.jmsservice = jmsservice;
        this.connectionId = dc.getConnectionId();
        this.isEnlisted = false;
        this.id = ++idCounter;
    }

    /**
     *  Commit the global transaction specified by xid.
     *
     *  @param  foreignXid A global transaction identifier
     *
     *  @param  onePhase If true, the resource manager should use a one-phase
     *          commit protocol to commit the work done on behalf of xid.
     *
     *  @throws XAException An error has occurred. Possible XAExceptions
     *          are XA_HEURHAZ, XA_HEURCOM, XA_HEURRB, XA_HEURMIX, XAER_RMERR,
     *          XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or XAER_PROTO.
     *
     *  <P>If the resource manager did not commit the transaction and the
     *  parameter onePhase is set to true, the resource manager may throw
     *  one of the XA_RB* exceptions. Upon return, the resource manager has
     *  rolled back the branch's work and has released all held resources.
     */
    public synchronized void commit(Xid foreignXid, boolean onePhase)
    throws XAException {

        if (_logger.isLoggable(Level.FINE)){
    		_logger.fine(_lgrMID_INF+"DirectXAResource ("+this.hashCode()+") Commit  "+printXid(foreignXid)+" (onePhase="+onePhase+")");
        }
    	
        //convert to XidImpl
        //XidImpl mqxid = new XidImpl(foreignXid);
        String methodName = "commit()";
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF + methodName + ":onePhase=" + onePhase +
                    ":transactionId=" + this.mTransactionId //+
                    //+"Xid="+mqxid.toString()
                    );
        }
            	
         JMSServiceReply reply = null;
         JMSServiceReply.Status status;
         try {
             reply = jmsservice.commitTransaction(this.connectionId,
                    this.mTransactionId, foreignXid,
                    (onePhase ? XAResource.TMONEPHASE : XAResource.TMNOFLAGS)
                    );
             this.setEnlisted(false);
             if (_logFINE){
                 _loggerJX.fine(_lgrMID_INF + methodName +
                        ":connectionId=" + this.connectionId +
                        ":committed transactionId=" + this.mTransactionId
                        //+"Xid="+mqxid.toString()
                        );
             }
         } catch (JMSServiceException jse) {
             status = jse.getJMSServiceReply().getStatus();
             String failure_cause;
             switch (status) {
                case CONFLICT:
                    failure_cause = "CONFLICT: "+jse.getCause().toString();
                    break;
                default:
                    failure_cause = "Unknown JMSService server error "+status+": "+jse.getCause().toString();
             }
             //XXX:tharakan:This message should be in the JMSServiceException
             String exerrmsg = 
                    "commitTransaction (XA) on JMSService:" +
                    jmsservice.getJMSServiceID() +
                    " failed for connectionId:"+ connectionId +
                    " and onePhase:" + onePhase +
                    //"and Xid:" + mqxid.toString() +
                    " due to " + failure_cause;
             _loggerOC.severe(exerrmsg);
             XAException xae = new XAException(XAException.XAER_RMERR);
             xae.initCause(jse);
             throw xae;
         } finally {
        	 // finish up this resource and any others joined to it in this transaction
        	 boolean throwExceptionIfNotFound = false;
        	 XidImpl savedXid = this.mXid;
        	 DirectXAResource[] resources = DirectXAResourceMap.getXAResources(this.mXid,throwExceptionIfNotFound);
			for (int i = 0; i < resources.length; i++) {
				DirectXAResource xari = resources[i];
				try {
					xari.clearTransactionInfo();
				} catch (JMSException jmse) {
					System.err.println("MQRA:DXAR:commit:XAException-Exception=" + jmse.getMessage());
					Debug.printStackTrace(jmse);
					XAException xae = new XAException(XAException.XAER_RMFAIL);
					xae.initCause(jmse);
					throw xae;
				}
			}
             DirectXAResourceMap.unregister(savedXid);
         }
    }
    
    public void clearTransactionInfo() throws JMSException{
    	dc.deleteTemporaryDestinationsIfClosed();

    	this.resourceState=CREATED;
        this.mTransactionId = 0L;
        this.mXid = null;
    }

    /**
     *  End the work performed on behalf of a transaction branch.
     *  The resource manager disassociates the XA resource from the
     *  transaction branch specified and lets the transaction
     *  complete.
     *
     *  <p>If TMSUSPEND is specified in the flags, the transaction branch
     *  is temporarily suspended in an incomplete state. The transaction
     *  context is in a suspended state and must be resumed via the
     *  <code>start</code> method with TMRESUME specified.</p>
     *
     *  <p>If TMFAIL is specified, the portion of work has failed.
     *  The resource manager may mark the transaction as rollback-only</p>
     *
     *  <p>If TMSUCCESS is specified, the portion of work has completed
     *  successfully.</p>
     *
     *  @param  foreignXid A global transaction identifier that is the same as
     *          the identifier used previously in the <code>start</code> method.
     *
     *  @param  flags One of TMSUCCESS, TMFAIL, or TMSUSPEND.
     *
     *  @throws XAException If an error has occurred. Possible XAException
     *          values are XAER_RMERR, XAER_RMFAILED, XAER_NOTA, XAER_INVAL,
     *          XAER_PROTO, or XA_RB*.
     */
    public synchronized void end(Xid foreignXid, int flags)
    throws XAException {
    	
        if (_logger.isLoggable(Level.FINE)){
    		_logger.fine(_lgrMID_INF+"DirectXAResource ("+this.hashCode()+") End     "+printXid(foreignXid)+printFlags(flags));
        }
    	
        String methodName = "end()";
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF + methodName + ":flags=" + flags +
                    ":transactionId=" + this.mTransactionId //+
                    //":Xid="+mqxid.toString()
                    );
        }
        
        // update the resource state
        if (isFail(flags)){
        	resourceState=FAILED;
        } else if (isSuspend(flags)){
        	resourceState=INCOMPLETE;
        } else {
        	resourceState=COMPLETE;
        }
        
        if (ResourceAdapter.isRevert6882044()) {
        	// revert to pre-6882044 behaviour and forward all events to broker
        	sendEndToBroker(foreignXid, flags);
        } else {
	        // now decide whether to send an END packet to the broker on the basis of the resource state
	        if (resourceState==COMPLETE ){
	        	// only send an END packet to the broker when all joined resources are complete
	        	// this works around Glassfish issue 7118
	        	boolean allComplete = true;
	       	 	DirectXAResource[] resources = DirectXAResourceMap.getXAResources(this.mXid,true);
	       	 	for (int i = 0; i < resources.length; i++) {
	       	 		DirectXAResource xari = resources[i]; 
	       	 		if (xari.getResourceState()!=COMPLETE){
	       	 			allComplete = false;
	       	 		}
	       	 	}
	       	 	if (allComplete){
	       	 		sendEndToBroker(foreignXid, flags);
	       	 	}
	        } else if (resourceState==FAILED){
	        	sendEndToBroker(foreignXid, flags);
	        } else if (resourceState==INCOMPLETE){
	        	// don't send the END to the broker
	        }

        }
    	    	
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF + methodName +
                   ":connectionId=" + this.connectionId +
                   ":ended transactionId=" + this.mTransactionId
                   //+"Xid="+mqxid.toString()
                   );
        }
    	
    }
    
    /**
     * Notify the broker than end() has been called
     * 
     * @param foreignXid
     * @param flags
     * @throws XAException
     */
    public synchronized void sendEndToBroker(Xid foreignXid, int flags)
    throws XAException {

        //convert to XidImpl
        //XidImpl mqxid = new XidImpl(foreignXid);
        String methodName = "endToBroker()";
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF + methodName + ":flags=" + flags +
                    ":transactionId=" + this.mTransactionId //+
                    //":Xid="+mqxid.toString()
                    );
        }
        JMSServiceReply reply = null;
        JMSServiceReply.Status status;
        try {
        		reply = jmsservice.endTransaction(this.connectionId,
                    this.mTransactionId, foreignXid, flags);
            if (_logFINE){
                _loggerJX.fine(_lgrMID_INF + methodName +
                       ":connectionId=" + this.connectionId +
                       ":ended transactionId=" + this.mTransactionId
                       //+"Xid="+mqxid.toString()
                       );
            }
        } catch (JMSServiceException jse) {
            status = jse.getJMSServiceReply().getStatus();
            String failure_cause;
            switch (status) {
                case CONFLICT:
                    failure_cause = "CONFLICT: "+jse.getCause().toString();
                    break;
                default:
                    failure_cause = "Unknown JMSService server error "+status+": "+jse.getCause().toString();
            }
            //XXX:tharakan:This message should be in the JMSServiceException
            String exerrmsg = 
                    "endTransaction (XA) on JMSService:" +
                    jmsservice.getJMSServiceID() +
                    " failed for connectionId:"+ connectionId +
                    //"and Xid:" + mqxid.toString() +
                    " and flags=" + flags +
                    " due to " + failure_cause;
            _loggerOC.severe(exerrmsg);
            XAException xae = new XAException(XAException.XAER_RMERR);
            xae.initCause(jse);
            throw xae;
        }
    }

    /**
     *  Tell the resource manager to forget about a heuristically
     *  completed transaction branch.
     *
     *  @param  foreignXid A global transaction identifier.
     *
     *  @throws XAException If an error has occurred. Possible exception
     *          values are XAER_RMERR, XAER_RMFAIL, XAER_NOTA, XAER_INVAL, or
     *          XAER_PROTO.
     */
    public void forget(Xid foreignXid)
    throws XAException {
        //convert to XidImpl
        //XidImpl mqxid = new XidImpl(foreignXid);
        if (_logFINE){
            _loggerJX.warning(_lgrMID_INF+"forget()UNSUPPORTED"+
                    ":Xid="+foreignXid.toString());
         }
        XidImpl xidToForget = new XidImpl(foreignXid);
        DirectXAResourceMap.unregister(xidToForget);
        if (mXid!=null){
        	if (mXid.equals(xidToForget)){
        		try {
					clearTransactionInfo();
				} catch (JMSException jmse) {
					System.err.println("MQRA:DXAR:forget:XAException-Exception=" + jmse.getMessage());
					Debug.printStackTrace(jmse);
					XAException xae = new XAException(XAException.XAER_RMFAIL);
					xae.initCause(jmse);
					throw xae;
				}
        	}
        }
    }

    /**
     *  Obtains the current transaction timeout value set for this
     *  XAResource instance. If <CODE>XAResource.setTransactionTimeout</CODE>
     *  was not used prior to invoking this method, the return value
     *  is the default timeout set for the resource manager; otherwise,
     *  the value used in the previous <CODE>setTransactionTimeout</CODE>
     *  call is returned.
     *
     *  @return The transaction timeout value in seconds.
     *
     *  @throws XAException If an error has occurred. Possible exception
     *          values are XAER_RMERR and XAER_RMFAIL.
     */
    public int getTransactionTimeout()
    throws XAException {
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF+"getTransactionTimeout() = 0");
        }
        return 0;
    }
    
    public int getResourceState() {
		return resourceState;
	}

    /**
     *  This method is called to determine if the resource manager
     *  instance represented by the target object is the same as the
     *  resouce manager instance represented by the parameter <i>xares</i>.
     *
     *  @param  foreignXaRes An XAResource object whose resource manager
     *          instance is to be compared with the resource manager instance
     *          of the target object.
     *
     *  @return <i>true</i> if it's the same RM instance; otherwise
     *          <i>false</i>.
     *
     *  @throws XAException If an error has occurred. Possible exception
     *          values are XAER_RMERR and XAER_RMFAIL.
     *
     */
    public boolean isSameRM(XAResource foreignXaRes)
    throws XAException {
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF+"isSameRM()");
        }
        if (ResourceAdapter.isSameRMAllowed() && (foreignXaRes instanceof DirectXAResource)){
            return true;
        } else {
            return false;
        }
    }

    /**
     *  Ask the resource manager to prepare for a transaction commit
     *  of the transaction specified in xid.
     *
     *  @param  foreignXid A global transaction identifier.
     *
     *  @return A value indicating the resource manager's vote on the
     *          outcome of the transaction. The possible values are: XA_RDONLY
     *          or XA_OK. If the resource manager wants to roll back the
     *          transaction, it should do so by raising an appropriate
     *          XAException in the prepare method.
     *
     *  @throws XAException If an error has occurred. Possible exception
     *          values are: XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_NOTA,
     *          XAER_INVAL, or XAER_PROTO.
     */
    public synchronized int prepare(Xid foreignXid)
    throws XAException {

        if (_logger.isLoggable(Level.FINE)){
    		_logger.fine(_lgrMID_INF+"DirectXAResource ("+this.hashCode()+") Prepare     "+printXid(foreignXid));
        }
    	
        //convert to XidImpl
        //XidImpl mqxid = new XidImpl(foreignXid);
        String methodName = "prepare()";
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF + methodName +
                    ":transactionId=" + this.mTransactionId //+
                    //:Xid="+mqxid.toString()
                    );
        }

        //JMS does not do RDONLY transactions
        int result = XA_OK;

        JMSServiceReply reply = null;
        JMSServiceReply.Status status;
        try {
            reply = jmsservice.prepareTransaction(this.connectionId,
                    this.mTransactionId, foreignXid);
            if (_logFINE){
                _loggerJX.fine(_lgrMID_INF + methodName +
                       ":connectionId=" + this.connectionId +
                       ":prepared transactionId=" + this.mTransactionId
                       //+"Xid="+mqxid.toString()
                       );
            }
        } catch (JMSServiceException jse) {
            status = jse.getJMSServiceReply().getStatus();
            String failure_cause;
            switch (status) {
                case CONFLICT:
                    failure_cause = "CONFLICT: "+jse.getCause().toString();
                    break;
                default:
                    failure_cause = "Unknown JMSService server error "+status+": "+jse.getCause().toString();
            }
            //XXX:tharakan:This message should be in the JMSServiceException
            String exerrmsg = 
                    "prepareTransaction (XA) on JMSService:" +
                    jmsservice.getJMSServiceID() +
                    " failed for connectionId:"+ connectionId +
                    //"and Xid:" + mqxid.toString() +
                    " due to " + failure_cause;
            _loggerOC.severe(exerrmsg);
            XAException xae = new XAException(XAException.XAER_RMERR);
            xae.initCause(jse);
            throw xae;
        }
        
        // update the resource state
        resourceState=PREPARED;
        
        return result;
    }

    /**
     *  Obtain a list of prepared transaction branches from a resource
     *  manager. The transaction manager calls this method during recovery
     *  to obtain the list of transaction branches that are currently in
     *  prepared or heuristically completed states.
     *
     *  @param  flags One of TMSTARTRSCAN, TMENDRSCAN, TMNOFLAGS. TMNOFLAGS
     *          must be used when no other flags are set in the parameter.
     *
     *  @return The resource manager returns zero or more XIDs of the
     *          transaction branches that are currently in a prepared or
     *          heuristically completed state. If an error occurs during the
     *          operation, the resource manager should throw the appropriate
     *          XAException.
     *
     *  @throws XAException If an error has occurred. Possible values are
     *          XAER_RMERR, XAER_RMFAIL, XAER_INVAL, and XAER_PROTO.
     */
    public Xid[] recover(int flags)
    throws XAException {
        if (_logFINE){
            //String methodName = "recover()";
            _loggerJX.fine(_lgrMID_INF+"recover():flags="+flags);
        }
        javax.transaction.xa.Xid[] result = null;
        JMSServiceReply.Status status;
        try {
            result = jmsservice.recoverXATransactions(this.connectionId, flags);
        } catch (JMSServiceException jse) {
            status = jse.getJMSServiceReply().getStatus();
            String failure_cause;
            switch (status) {
                case CONFLICT:
                    failure_cause = "CONFLICT: "+jse.getCause().toString();
                    break;
                default:
                    failure_cause = "Unknown JMSService server error "+status+": "+jse.getCause().toString();
            }
            //XXX:tharakan:This message should be in the JMSServiceException
            String exerrmsg = 
                    "recoverXATransactions (XA) on JMSService:" +
                    jmsservice.getJMSServiceID() +
                    " failed for connectionId:"+ connectionId +
                    " due to " + failure_cause;
            _loggerOC.severe(exerrmsg);
            XAException xae = new XAException(XAException.XAER_RMERR);
            xae.initCause(jse);
            throw xae;
        }
        return result;
    }

    /**
     *  Inform the resource manager to roll back work done on behalf
     *  of a transaction branch.
     *
     *  @param  foreignXid A global transaction identifier.
     *
     *  @throws XAException If an error has occurred.
     */
    public synchronized void rollback(Xid foreignXid)
    throws XAException {
    	
        if (_logger.isLoggable(Level.FINE)){
    		_logger.fine(_lgrMID_INF+"DirectXAResource ("+this.hashCode()+") Rollback  "+printXid(foreignXid)+")");
        }  
    	
        //convert to XidImpl
        //XidImpl mqxid = new XidImpl(foreignXid);
        String methodName = "rollback()";
        if (_logFINE){
            _loggerJX.fine(_lgrMID_INF + methodName +
                    ":transactionId="+ this.mTransactionId //+
                    //":Xid="+mqxid.toString());
                    );
        }
                      
        JMSServiceReply reply = null;
        JMSServiceReply.Status status;
        try {
            reply = jmsservice.rollbackTransaction(this.connectionId,
                    this.mTransactionId, foreignXid,
                    true, true
                    );
            this.setEnlisted(false);
            if (_logFINE){
                _loggerJX.fine(_lgrMID_INF + methodName +
                       ":connectionId=" + this.connectionId +
                       ":rolled back transactionId=" + this.mTransactionId
                       //+"Xid="+mqxid.toString()
                       );
            }
        } catch (JMSServiceException jse) {
            status = jse.getJMSServiceReply().getStatus();
            String failure_cause;
            switch (status) {
                case CONFLICT:
                    failure_cause = "CONFLICT: "+jse.getCause().toString();
                    break;
                default:
                    failure_cause = "Unknown JMSService server error "+status+": "+jse.getCause();
            }
            //XXX:tharakan:This message should be in the JMSServiceException
            String exerrmsg = 
                    "rollbackTransaction (XA) on JMSService:" +
                    jmsservice.getJMSServiceID() +
                    " failed for connectionId:"+ connectionId +
                    ":transactionId=" + this.mTransactionId +
                    //"and Xid:" + mqxid.toString() +
                    " due to " + failure_cause;
            _loggerOC.severe(exerrmsg);
            XAException xae = new XAException(XAException.XAER_RMERR);
            xae.initCause(jse);
            throw xae;
        } finally {
       	 	// finish up this resource and any others joined to it in this transaction
        	boolean throwExceptionIfNotFound = false;
       	 	DirectXAResource[] resources = DirectXAResourceMap.getXAResources(this.mXid,throwExceptionIfNotFound);
       	    XidImpl savedXid = this.mXid;
       	 	for (int i = 0; i < resources.length; i++) {
       	 		DirectXAResource xari = resources[i]; 
       	 		try {
					xari.clearTransactionInfo();
				} catch (JMSException jmse) {
					System.err.println("MQRA:DXAR:rollback:XAException-Exception=" + jmse.getMessage());
					Debug.printStackTrace(jmse);
					XAException xae = new XAException(XAException.XAER_RMFAIL);
					xae.initCause(jmse);
					throw xae;
				}
       	 	}
            DirectXAResourceMap.unregister(savedXid);   
        }
    }

    /**
     *  Set the current transaction timeout value for this <CODE>XAResource</CODE>
     *  instance. Once set, this timeout value is effective until
     *  <code>setTransactionTimeout</code> is invoked again with a different
     *  value. To reset the timeout value to the default value used by the resource
     *  manager, set the value to zero.
     *
     *  If the timeout operation is performed successfully, the method returns
     *  <i>true</i>; otherwise <i>false</i>. If a resource manager does not
     *  support explicitly setting the transaction timeout value, this method
     *  returns <i>false</i>.
     *
     *  @param  transactionTimeout The transaction timeout value in seconds.
     *
     *  @return <i>true</i> if the transaction timeout value is set successfully;
     *          otherwise <i>false</i>.
     *
     *  @throws XAException If an error has occurred. Possible exception values
     *  are XAER_RMERR, XAER_RMFAIL, or XAER_INVAL.
     */
    public boolean setTransactionTimeout(int transactionTimeout)
    throws XAException {
        if (_logFINE){
            //String methodName = "setTransactionTimeout()";
            _loggerJX.fine(_lgrMID_INF+"setTransactionTimeout()="+
                    transactionTimeout+
                    ":returning false.");
        }
        return false;
    }
    
    /**
     *  Start work on behalf of a transaction branch specified in
     *  <code>foreignXid</code>.
     *
     *  If TMJOIN is specified, the start applies to joining a transaction
     *  previously seen by the resource manager. If TMRESUME is specified,
     *  the start applies to resuming a suspended transaction specified in the
     *  parameter <code>foreignXid</code>.
     *
     *  If neither TMJOIN nor TMRESUME is specified and the transaction
     *  specified by <code>foreignXid</code> has previously been seen by the resource
     *  manager, the resource manager throws the XAException exception with
     *  XAER_DUPID error code.
     *
     *  @param  foreignXid A global transaction identifier to be associated
     *          with the resource.
     *
     *  @param  flags One of TMNOFLAGS, TMJOIN, or TMRESUME.
     *
     *  @throws XAException If an error has occurred. Possible exceptions
     *          are XA_RB*, XAER_RMERR, XAER_RMFAIL, XAER_DUPID, XAER_OUTSIDE,
     *          XAER_NOTA, XAER_INVAL, or XAER_PROTO.
     *
     */
    public synchronized void start(Xid foreignXid, int flags) throws XAException {
    	
        if (_logger.isLoggable(Level.FINE)){
    		_logger.fine(_lgrMID_INF+"DirectXAResource ("+this.hashCode()+") Start   "+printXid(foreignXid)+printFlags(flags));
        }
    	
		// convert to XidImpl
		// XidImpl mqxid = new XidImpl(foreignXid);
		JMSServiceReply.Status status;
		JMSServiceReply reply = null;
		long transactionId = 0L;
		String methodName = "start()";
		if (_logFINE) {
			_loggerJX.fine(_lgrMID_INF + methodName + ":flags=" + flags + ":connectioId=" + this.connectionId
			// +"Xid="+mqxid.toString()
					);
		}
		
        // if we're reverting to the pre-6882044 behaviour always send the START to the broker
        // otherwise send the START to the broker only if this is not a TMRESUME
	    if (!isResume(flags) || ResourceAdapter.isRevert6882044()){
	    	transactionId = sendStartToBroker(foreignXid, flags, reply, transactionId);
	        validateAndSaveXidTransactionID(foreignXid, transactionId);
	        DirectXAResourceMap.register(mXid, this,isJoin(flags));
	    }
	     
		this.setEnlisted(true);
		if (_logFINE) {
			_loggerJX.fine(_lgrMID_INF + methodName + ":connectionId=" + this.connectionId + ":started transactionId="
					+ this.mTransactionId
			// +"Xid="+mqxid.toString()
					);
		}
	}

	private long sendStartToBroker(Xid foreignXid, int flags, JMSServiceReply reply, long transactionId)
			throws XAException {
		JMSServiceReply.Status status;
		try {
			// We must be sessionId agnostic
			reply = jmsservice.startTransaction(connectionId, 0L, foreignXid, flags,
					JMSService.TransactionAutoRollback.UNSPECIFIED, 0L);
			try {
				transactionId = reply.getJMQTransactionID();
			} catch (NoSuchFieldException nsfe) {
				String exerrmsg = _lgrMID_EXC + "JMSServiceException:Missing JMQTransactionID";
				XAException xae = new XAException(XAException.XAER_RMFAIL);
				xae.initCause(nsfe);
				_loggerOC.severe(exerrmsg);
				throw xae;
			}

		} catch (JMSServiceException jse) {
			status = jse.getJMSServiceReply().getStatus();
			String failure_cause;
			switch (status) {
			case NOT_IMPLEMENTED:
				failure_cause = "TransactionAutoRollback not implemented.";
				break;
			case CONFLICT:
				failure_cause = "CONFLICT: " + jse.getCause().toString();
				break;
			default:
				failure_cause = "Unknown JMSService server error " + status + ": " + jse.getCause().toString();
			}
			String exerrmsg = "startTransaction (XA) on JMSService:" + jmsservice.getJMSServiceID()
					+ " failed for connectionId:" + connectionId +
					// "and Xid:" + mqxid.toString() +
					" due to " + failure_cause;
			_loggerOC.severe(exerrmsg);
			XAException xae = new XAException(XAException.XAER_RMFAIL);
			xae.initCause(jse);
			throw xae;
		}
		return transactionId;
	}

	/**
	 * Validate the specified foreignXid and transactionID
	 * Convert the specified foreignXid to our own implementation
	 * and save in the fields mTransactionId and mXid
	 * 
	 * @param foreignXid
	 * @param transactionId
	 */
	private void validateAndSaveXidTransactionID(Xid foreignXid, long transactionId) {
		assert transactionId != 0L;
		if ((this.mTransactionId == 0L) && (this.mXid == null)) {
			this.mTransactionId = transactionId;
			this.mXid = new XidImpl(foreignXid); // mqxid;
		} else {
			if (this.mXid.equals(foreignXid)) {
				if (this.mTransactionId != transactionId) {
					System.out.println("DXAR:start():Warning:" + "Received diff txId for same Xid:"
							+ "switching transactionId:" + "\nDXAR TXid=" + this.mTransactionId + "\ngot  TXid="
							+ transactionId + "\nFor   Xid=" + printXid(mXid));
					this.mTransactionId = transactionId;
				}
			} else {
				System.out
						.println("DXAR:start():Warning:" + "Received diff Xid for open txnId:"
								+ "switching transactionId:" + "\nDXAR  Xid=" + printXid(mXid) + "\nDXAR TXid="
								+ this.mTransactionId + "\ngot   Xid=" + printXid(foreignXid) + "\ngot  TXid="
								+ transactionId);
				// remove transactionId and xid as it will be wrong if used
				this.mTransactionId = transactionId;
				this.mXid = new XidImpl(foreignXid); // mqxid;
			}
		}
	}

    /**
     *  Set the enlisted flag on this DirectXAResource
     *
     *  @param  value The boolean indicating whether it is enlisted or not
     */
    public synchronized void setEnlisted(boolean value) {
        this.isEnlisted = value;
        //Only change the enlisted state on the connection if it is not an MDB
        if (!this.usedByMDB  &&  (this.dc != null)) {
            this.dc.setEnlisted(value);
        }
    }

    protected void _setUsedByMDB(boolean value){
        this.usedByMDB = value;
    }

    /**
     *  Get the value of the enlisted flag for this DirectXAResource
     *
     *  @return The enlisted state of this DirectXAResource.
     *          {@code true} If it has been enlisted in an XA transaction.<b>
     *          {@code false} If it currently not enlistedin an XA transaction.
     */
    public synchronized boolean isEnlisted() {
        return this.isEnlisted;
    }
    
    public synchronized void setRollback(boolean state, Throwable cause){
        
    }

    public int _getId(){
        return this.id;
    }

    public long _getTransactionId(){
        return this.mTransactionId;
    }
    
    // Used for debugging only
    private String printXid(Xid foreignXid){
    	return ("(GlobalTransactionID="+foreignXid.getGlobalTransactionId()) +
    	        ", BranchQualifier="+foreignXid.getBranchQualifier()+") ";
    }
    
    private boolean isJoin(int flags){
    	return((flags & XAResource.TMJOIN) == XAResource.TMJOIN);
    }
    
    private boolean isResume(int flags){
    	return((flags & XAResource.TMRESUME) == XAResource.TMRESUME);
    }
    
    // Used for debugging only
    private boolean isNoFlags(int flags){
    	return((flags & XAResource.TMNOFLAGS) == XAResource.TMNOFLAGS);
    }
    // Used for debugging only
    private boolean isFail(int flags){
    	return((flags & XAResource.TMFAIL) == XAResource.TMFAIL);
    }
    // Used for debugging only
    private boolean isOnePhase(int flags){
    	return((flags & XAResource.TMONEPHASE) == XAResource.TMONEPHASE);
    }
    // Used for debugging only
    private boolean isSuccess(int flags){
    	return((flags & XAResource.TMSUCCESS) == XAResource.TMSUCCESS);
    }
    // Used for debugging only
    private boolean isSuspend(int flags){
    	return((flags & XAResource.TMSUSPEND) == XAResource.TMSUSPEND);
    }    
    // Used for debugging only
    private boolean isTMENDRSCAN(int flags){
    	return((flags & XAResource.TMENDRSCAN) == XAResource.TMENDRSCAN);
    }      
    // Used for debugging only
    private boolean TMSTARTRSCAN(int flags){
    	return((flags & XAResource.TMSTARTRSCAN) == XAResource.TMSTARTRSCAN);
    }  
    
    // Used for debugging only
    private String printFlags(int flags){
    	String result = ("(Flags: ");
        if (isJoin(flags)){
        	result=result+("JOIN ");
        }
        if (isNoFlags(flags)){
        	result=result+("TMNOFLAGS ");
        }
        if (isFail(flags)){
        	result=result+("TMFAIL ");
        }
        if (isOnePhase(flags)){
        	result=result+("TMONEPHASE ");
        }
        if (isResume(flags)){
        	result=result+("TMRESUME ");
        }
        if (isSuccess(flags)){
        	result=result+("TMSUCCESS ");
        }
        if (isSuspend(flags)){
        	result=result+("TMSUSPEND ");
        }    
        if (isTMENDRSCAN(flags)){
        	result=result+("TMENDRSCAN ");
        }      
        if (TMSTARTRSCAN(flags)){
        	result=result+("TMSTARTRSCAN ");
        } 
        result=result+(")");
        return result;
    }
    
}
