Package org.activemq

Source Code of org.activemq.TransactionContext

/**
*
* Copyright 2004 Protique Ltd
* Copyright 2004 Hiram Chirino
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**/
package org.activemq;

import java.util.ArrayList;

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

import org.activemq.message.ActiveMQXid;
import org.activemq.message.IntResponseReceipt;
import org.activemq.message.ResponseReceipt;
import org.activemq.message.TransactionInfo;
import org.activemq.message.XATransactionInfo;
import org.activemq.util.IdGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* A TransactionContext provides the means to control a JMS transaction.  It provides
* a local transaction interface and also an XAResource interface.
*
* <p/>
* An application server controls the transactional assignment of an XASession
* by obtaining its XAResource. It uses the XAResource to assign the session
* to a transaction, prepare and commit work on the transaction, and so on.
* <p/>
* An XAResource provides some fairly sophisticated facilities for
* interleaving work on multiple transactions, recovering a list of
* transactions in progress, and so on. A JTA aware JMS provider must fully
* implement this functionality. This could be done by using the services of a
* database that supports XA, or a JMS provider may choose to implement this
* functionality from scratch.
* <p/>
*
* @version $Revision: 1.1.1.1 $
* @see javax.jms.Session
* @see javax.jms.QueueSession
* @see javax.jms.TopicSession
* @see javax.jms.XASession
*/
public class TransactionContext implements XAResource {
   
    private static final Log log = LogFactory.getLog(TransactionContext.class);
   
    private final ActiveMQConnection connection;
    private final ArrayList sessions = new ArrayList(2);
    private final IdGenerator localTransactionIdGenerator = new IdGenerator();

    // To track XA transactions.
    private Xid associatedXid;
    private ActiveMQXid activeXid;

    // To track local transactions.
    private String localTransactionId;

    private LocalTransactionEventListener localTransactionEventListener;
   
    public TransactionContext(ActiveMQConnection connection) {
        this.connection = connection;       
    }
   
    public boolean isInXATransaction() {
        return associatedXid!=null;
    }
   
    public boolean isInLocalTransaction() {
        return localTransactionId!=null;
    }
   
    /**
     * @return Returns the localTransactionEventListener.
     */
    public LocalTransactionEventListener getLocalTransactionEventListener() {
        return localTransactionEventListener;
    }

    /**
     * Used by the resource adapter to listen to transaction events.
     *
     * @param localTransactionEventListener The localTransactionEventListener to set.
     */
    public void setLocalTransactionEventListener(LocalTransactionEventListener localTransactionEventListener) {
        this.localTransactionEventListener = localTransactionEventListener;
    }

    /////////////////////////////////////////////////////////////
    //
    // Methods that interface with the session
    //
    /////////////////////////////////////////////////////////////
    public void addSession(ActiveMQSession session) {
        sessions.add(session);
    }
    public void removeSession(ActiveMQSession session) {
        sessions.remove(session);
    }
   
    private void postRollback() {
        int size = sessions.size();       
        for(int i=0; i < size; i++ ){
            ((ActiveMQSession)sessions.get(i)).redeliverUnacknowledgedMessages(true);
        }
    }

    private void postCommit() {
        int size = sessions.size();       
        for(int i=0; i < size; i++ ){
            ((ActiveMQSession)sessions.get(i)).clearDeliveredMessages();
        }
    }

    public Object getTransactionId() {
        if( localTransactionId!=null )
            return localTransactionId;
        return activeXid;
    }
       
    /////////////////////////////////////////////////////////////
    //
    // Local transaction interface.
    //
    /////////////////////////////////////////////////////////////       
   
    /**
     * Start a local transaction.
     */
    public void begin() throws JMSException {       
        if( associatedXid!=null )
            throw new TransactionInProgressException("Cannot start local transction.  XA transaction is allready in progress.");       
       
        if( localTransactionId==null ) {        
            this.localTransactionId = localTransactionIdGenerator.generateId();
            TransactionInfo info = new TransactionInfo();
            info.setTransactionId((String)localTransactionId);
            info.setType(TransactionInfo.START);
            this.connection.asyncSendPacket(info);
           
            // Notify the listener that the tx was started.
            if (localTransactionEventListener != null) {
                localTransactionEventListener.beginEvent();
            }
            if( log.isDebugEnabled() )
                log.debug("Started local transaction: "+localTransactionId);
        }
    }
   
    /**
     * Rolls back any messages done in this transaction and releases any locks currently held.
     *
     * @throws JMSException if the JMS provider fails to roll back the transaction due to some internal error.
     * @throws javax.jms.IllegalStateException if the method is not called by a transacted session.
     */
    public void rollback() throws JMSException {
        if( associatedXid!=null )
            throw new TransactionInProgressException("Cannot rollback() if an XA transaction is allready in progress ");
       
        if( localTransactionId!=null ) {           
            TransactionInfo info = new TransactionInfo();
            info.setTransactionId((String)localTransactionId);
            info.setType(TransactionInfo.ROLLBACK);
            //before we send, update the current transaction id
            this.localTransactionId = null;
            this.connection.asyncSendPacket(info);
            // Notify the listener that the tx was rolled back
            if (localTransactionEventListener != null) {
                localTransactionEventListener.rollbackEvent();
            }
            if( log.isDebugEnabled() )
                log.debug("Rolledback local transaction: "+localTransactionId);
        }
        postRollback();
    }
       
    /**
     * Commits all messages done in this transaction and releases any locks currently held.
     *
     * @throws JMSException if the JMS provider fails to commit the transaction due to some internal error.
     * @throws TransactionRolledBackException if the transaction is rolled back due to some internal error during
     * commit.
     * @throws javax.jms.IllegalStateException if the method is not called by a transacted session.
     */
    public void commit() throws JMSException {
        if( associatedXid!=null )
            throw new TransactionInProgressException("Cannot commit() if an XA transaction is allready in progress ");

        // Only send commit if the transaction was started.
        if (localTransactionId!=null) {
            TransactionInfo info = new TransactionInfo();
            info.setTransactionId((String)localTransactionId);
            info.setType(TransactionInfo.COMMIT);
            //before we send, update the current transaction id
            this.localTransactionId = null;
            // Notify the listener that the tx was commited back
            this.connection.syncSendPacket(info);
            if (localTransactionEventListener != null) {
                localTransactionEventListener.commitEvent();
            }
            if( log.isDebugEnabled() )
                log.debug("Committed local transaction: "+localTransactionId);
        }
        postCommit();
    }
   
    /////////////////////////////////////////////////////////////
    //
    // XAResource Implementation
    //
    /////////////////////////////////////////////////////////////
    /**
     * Associates a transaction with the resource.
     */
    public void start(Xid xid, int flags) throws XAException {
        if( localTransactionId!=null )
            throw new XAException(XAException.XAER_PROTO);
       
        // Are we allready associated?
        if (associatedXid != null) {
            throw new XAException(XAException.XAER_PROTO);
        }

        if ((flags & TMJOIN) == TMJOIN) {
            // TODO: verify that the server has seen the xid
        }
        if ((flags & TMJOIN) == TMRESUME) {
            // TODO: verify that the xid was suspended.
        }

        // associate
        setXid(xid);

    }

    public void end(Xid xid, int flags) throws XAException {
        if( localTransactionId!=null )
            throw new XAException(XAException.XAER_PROTO);

        if ((flags & TMSUSPEND) == TMSUSPEND) {
            // You can only suspend the associated xid.
            if (associatedXid == null || !ActiveMQXid.equals(associatedXid,xid)) {
                throw new XAException(XAException.XAER_PROTO);
            }

            //TODO: we may want to put the xid in a suspended list.
            setXid(null);
        } else if ((flags & TMFAIL) == TMFAIL) {
            //TODO: We need to rollback the transaction??
            setXid(null);
        } else if ((flags & TMSUCCESS) == TMSUCCESS) {
            //set to null if this is the current xid.
            //otherwise this could be an asynchronous success call
            if (ActiveMQXid.equals(associatedXid,xid)) {
                setXid(null);
            }
        } else {
            throw new XAException(XAException.XAER_INVAL);
        }
        if( log.isDebugEnabled() )
            log.debug("Ended XA transaction: "+activeXid);

    }

    public int prepare(Xid xid) throws XAException {

        // We allow interleaving multiple transactions, so
        // we don't limit prepare to the associated xid.
        ActiveMQXid x;
        //THIS SHOULD NEVER HAPPEN because end(xid, TMSUCCESS) should have been called first
        if (ActiveMQXid.equals(associatedXid,xid)) {
            throw new XAException(XAException.XAER_PROTO);
        } else {
            //TODO cache the known xids so we don't keep recreating this one??
            x = new ActiveMQXid(xid);
        }

        XATransactionInfo info = new XATransactionInfo();
        info.setXid(x);
        info.setType(XATransactionInfo.PRE_COMMIT);

        try {
            if( log.isDebugEnabled() )
                log.debug("Preparing XA transaction: "+x);
           
            // Find out if the server wants to commit or rollback.
            IntResponseReceipt receipt = (IntResponseReceipt) this.connection.syncSendRequest(info);
            return receipt.getResult();
        } catch (JMSException e) {
            throw toXAException(e);
        }
    }

    public void rollback(Xid xid) throws XAException {

        // We allow interleaving multiple transactions, so
        // we don't limit rollback to the associated xid.
        ActiveMQXid x;
        if (ActiveMQXid.equals(associatedXid,xid)) {
            //I think this can happen even without an end(xid) call.  Need to check spec.
            x = activeXid;
        } else {
            x = new ActiveMQXid(xid);
        }

        XATransactionInfo info = new XATransactionInfo();
        info.setXid(x);
        info.setType(XATransactionInfo.ROLLBACK);

        try {
            if( log.isDebugEnabled() )
                log.debug("Rollingback XA transaction: "+x);
           
            // Let the server know that the tx is rollback.
            this.connection.syncSendPacket(info);
        } catch (JMSException e) {
            throw toXAException(e);
        }
       
        postRollback();
    }

    // XAResource interface
    public void commit(Xid xid, boolean onePhase) throws XAException {

        // We allow interleaving multiple transactions, so
        // we don't limit commit to the associated xid.
        ActiveMQXid x;
        if (ActiveMQXid.equals(associatedXid,xid)) {
            //should never happen, end(xid,TMSUCCESS) must have been previously called
            throw new XAException(XAException.XAER_PROTO);
        } else {
            x = new ActiveMQXid(xid);
        }

        XATransactionInfo info = new XATransactionInfo();
        info.setXid(x);
        info.setType(onePhase ? XATransactionInfo.COMMIT_ONE_PHASE : XATransactionInfo.COMMIT);

        try {
            if( log.isDebugEnabled() )
                log.debug("Committing XA transaction: "+x);
           
            // Notify the server that the tx was commited back
            this.connection.syncSendPacket(info);
        } catch (JMSException e) {
            throw toXAException(e);
        }

        postCommit();
    }


    public void forget(Xid xid) throws XAException {

        // We allow interleaving multiple transactions, so
        // we don't limit forget to the associated xid.
        ActiveMQXid x;
        if (ActiveMQXid.equals(associatedXid,xid)) {
            //TODO determine if this can happen... I think not.
            x = activeXid;
        } else {
            x = new ActiveMQXid(xid);
        }

        XATransactionInfo info = new XATransactionInfo();
        info.setXid(x);
        info.setType(XATransactionInfo.FORGET);

        try {
            if( log.isDebugEnabled() )
                log.debug("Forgetting XA transaction: "+x);
           
            // Tell the server to forget the transaction.
            this.connection.syncSendPacket(info);
        } catch (JMSException e) {
            throw toXAException(e);
        }
    }

    public boolean isSameRM(XAResource xaResource) throws XAException {
        if (xaResource == null) {
            return false;
        }
        if (!(xaResource instanceof TransactionContext)) {
            return false;
        }
        TransactionContext xar = (TransactionContext) xaResource;
        try {
            return getResourceManagerId().equals(xar.getResourceManagerId());
        } catch (Throwable e) {
            throw (XAException)new XAException("Could not get resource manager id.").initCause(e);
        }
    }


    public Xid[] recover(int flag) throws XAException {

        XATransactionInfo info = new XATransactionInfo();
        info.setType(XATransactionInfo.XA_RECOVER);

        try {
            ResponseReceipt receipt = (ResponseReceipt) this.connection.syncSendRequest(info);
            return (ActiveMQXid[]) receipt.getResult();
        } catch (JMSException e) {
            throw toXAException(e);
        }
    }

    public int getTransactionTimeout() throws XAException {

        XATransactionInfo info = new XATransactionInfo();
        info.setType(XATransactionInfo.GET_TX_TIMEOUT);

        try {
            // get the tx timeout that was set.
            IntResponseReceipt receipt = (IntResponseReceipt) this.connection.syncSendRequest(info);
            return receipt.getResult();
        } catch (JMSException e) {
            throw toXAException(e);
        }
    }

    public boolean setTransactionTimeout(int seconds) throws XAException {

        XATransactionInfo info = new XATransactionInfo();
        info.setType(XATransactionInfo.SET_TX_TIMEOUT);
        info.setTransactionTimeout(seconds);

        try {
            // Setup the new tx timeout
            this.connection.asyncSendPacket(info);
            return true;
        } catch (JMSException e) {
            throw toXAException(e);
        }
    }
   
    /////////////////////////////////////////////////////////////
    //
    // Helper methods.
    //
    /////////////////////////////////////////////////////////////
    private String getResourceManagerId() throws JMSException {
        return this.connection.getResourceManagerId();
    }
   
    private void setXid(Xid xid) throws XAException {
        if (xid != null) {
            // associate
            associatedXid = xid;
            activeXid = new ActiveMQXid(xid);
           
            XATransactionInfo info = new XATransactionInfo();
            info.setXid(activeXid);
            info.setType(XATransactionInfo.START);
            try {
                this.connection.asyncSendPacket(info);
                if( log.isDebugEnabled() )
                    log.debug("Started XA transaction: "+activeXid);
            } catch (JMSException e) {
                throw toXAException(e);
            }
           
        } else {
           
            if( activeXid!=null ) {
                XATransactionInfo info = new XATransactionInfo();
                info.setXid(activeXid);
                info.setType(XATransactionInfo.END);
                try {
                    this.connection.syncSendPacket(info);
                    if( log.isDebugEnabled() )
                        log.debug("Ended XA transaction: "+activeXid);
                } catch (JMSException e) {
                    throw toXAException(e);
                }
            }
           
            // dis-associate
            associatedXid = null;
            activeXid = null;
        }
    }

    /**
     * Converts a JMSException from the server to an XAException.
     * if the JMSException contained a linked XAException that is
     * returned instead.
     *
     * @param e
     * @return
     */
    private XAException toXAException(JMSException e) {
        if (e.getCause() != null && e.getCause() instanceof XAException) {
            XAException original = (XAException) e.getCause();
            XAException xae = new XAException(original.getMessage());
            xae.errorCode = original.errorCode;
            xae.initCause(original);
            return xae;
        }

        XAException xae = new XAException(e.getMessage());
        xae.errorCode = XAException.XAER_RMFAIL;
        xae.initCause(e);
        return xae;
    }

    public ActiveMQConnection getConnection() {
        return connection;
    }

}
TOP

Related Classes of org.activemq.TransactionContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.