Package org.jboss.jms.tx

Source Code of org.jboss.jms.tx.ResourceManager

/*
  * JBoss, Home of Professional Open Source
  * Copyright 2005, JBoss Inc., and individual contributors as indicated
  * by the @authors tag. See the copyright.txt in the distribution for a
  * full listing of individual contributors.
  *
  * This is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as
  * published by the Free Software Foundation; either version 2.1 of
  * the License, or (at your option) any later version.
  *
  * This software is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  * Lesser General Public License for more details.
  *
  * You should have received a copy of the GNU Lesser General Public
  * License along with this software; if not, write to the Free
  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
  */
package org.jboss.jms.tx;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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

import org.jboss.jms.delegate.ConnectionDelegate;
import org.jboss.jms.delegate.SessionDelegate;
import org.jboss.jms.message.JBossMessage;
import org.jboss.jms.message.MessageProxy;
import org.jboss.jms.server.endpoint.DeliveryInfo;
import org.jboss.jms.tx.ClientTransaction.SessionTxState;
import org.jboss.jms.util.MessagingTransactionRolledBackException;
import org.jboss.jms.util.MessagingXAException;
import org.jboss.logging.Logger;

import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;

/**
* The ResourceManager manages work done in both local and global (XA) transactions.
*
* This is one instance of ResourceManager per JMS server. The ResourceManager instances are managed
* by ResourceManagerFactory.
*
* @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a>
* @author <a href="mailto:Konda.Madhu@uk.mizuho-sc.com">Madhu Konda</a>
* @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>
* @author <a href="mailto:Cojonudo14@hotmail.com">Hiram Chirino</a>
* @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
* @version $Revision: 2470 $
*
* $Id: ResourceManager.java 2470 2007-02-27 19:33:48Z timfox $
*/
public class ResourceManager
{
   // Constants ------------------------------------------------------------------------------------
  
   // Attributes -----------------------------------------------------------------------------------
  
   private boolean trace = log.isTraceEnabled();
  
   private ConcurrentHashMap transactions = new ConcurrentHashMap();
  
   // Static ---------------------------------------------------------------------------------------
  
   private static final Logger log = Logger.getLogger(ResourceManager.class);
  
   // Constructors ---------------------------------------------------------------------------------
  
   ResourceManager()
   {     
   }
   
   // Public ---------------------------------------------------------------------------------------
  
   /*
    * Merge another resource manager into this one - used in failover
    */
   public void merge(ResourceManager other)
   {
      transactions.putAll(other.transactions);
   }
  
   /**
    * Remove a tx
    */
   public ClientTransaction removeTx(Object xid)
   {
      return removeTxInternal(xid);
   }
           
   /**
    * Create a local tx.
    */
   public LocalTx createLocalTx()
   {
      ClientTransaction tx = new ClientTransaction();
     
      LocalTx xid = getNextTxId();
     
      transactions.put(xid, tx);
     
      return xid;
   }
  
   /**
    * Add a message to a transaction
    *
    * @param xid - The id of the transaction to add the message to
    * @param m The message
    */
   public void addMessage(Object xid, int sessionId, JBossMessage m)
   {
      if (trace) { log.trace("addding message " + m + " for xid " + xid); }
     
      ClientTransaction tx = getTxInternal(xid);
     
      tx.addMessage(sessionId, m);
   }
  
   /*
    * Failover session from old session ID -> new session ID
    */
   public void handleFailover(int newServerID, int oldSessionID, int newSessionID)
   {
      for(Iterator i = this.transactions.values().iterator(); i.hasNext(); )
      {
         ClientTransaction tx = (ClientTransaction)i.next();
         tx.handleFailover(newServerID, oldSessionID, newSessionID);
      }               
   }  
  
   /*
    * Get all the deliveries corresponding to the session ID
    */
   public List getDeliveriesForSession(int sessionID)
   {
      List ackInfos = new ArrayList();

      for(Iterator i = transactions.values().iterator(); i.hasNext(); )
      {
         ClientTransaction tx = (ClientTransaction)i.next();
         List acks = tx.getDeliveriesForSession(sessionID);
        
         ackInfos.addAll(acks);
      }

      return ackInfos;
   }
  
  
   /**
    * Add an acknowledgement to the transaction
    *
    * @param xid - The id of the transaction to add the message to
    * @param ackInfo Information describing the acknowledgement
    */
   public void addAck(Object xid, int sessionId, DeliveryInfo ackInfo) throws JMSException
   {
      if (trace) { log.trace("adding " + ackInfo + " to transaction " + xid); }
     
      ClientTransaction tx = getTxInternal(xid);
     
      if (tx == null)
      {
         throw new JMSException("There is no transaction with id " + xid);
      }
     
      tx.addAck(sessionId, ackInfo);
   }
        
   public void commitLocal(LocalTx xid, ConnectionDelegate connection) throws JMSException
   {
      if (trace) { log.trace("committing " + xid); }
     
      ClientTransaction tx = this.getTxInternal(xid);
     
      checkAndRollbackJMS(tx, xid);
     
      // Invalid xid
      if (tx == null)
      {
         throw new IllegalStateException("Cannot find transaction " + xid);
      }
                 
      TransactionRequest request =
         new TransactionRequest(TransactionRequest.ONE_PHASE_COMMIT_REQUEST, null, tx);
     
      try
      {
         connection.sendTransaction(request, false);
        
         // If we get this far we can remove the transaction
        
         if (this.removeTxInternal(xid) == null)
         {
            throw new IllegalStateException("Cannot find xid to remove " + xid);
         }
      }
      catch (Throwable t)
      {
         // If a problem occurs during commit processing the session should be rolled back
         rollbackLocal(xid);
        
         JMSException e = new MessagingTransactionRolledBackException(t.getMessage());
         e.initCause(t);
         throw e;        
      }
   }
  
   public void rollbackLocal(Object xid) throws JMSException
   {
      if (trace) { log.trace("rolling back local xid " + xid); }
     
      ClientTransaction ts = removeTxInternal(xid);
     
      if (ts == null)
      {     
         throw new IllegalStateException("Cannot find transaction with xid:" + xid);        
      }
     
      // don't need messages for rollback
      // We don't clear the acks since we need to redeliver locally
      ts.clearMessages();
     
      // for one phase rollback there is nothing to do on the server
     
      redeliverMessages(ts);
   }
  
   //Only used for testing
   public ClientTransaction getTx(Object xid)
   {
      return getTxInternal(xid);
   }
  
   //Only used for testing
   public int size()
   {
      return transactions.size();
   }
     
  
   public boolean checkForAcksInSession(int sessionId)
   {        
      Iterator iter = transactions.entrySet().iterator();
     
      while (iter.hasNext())
      {        
         Map.Entry entry = (Map.Entry)iter.next();
     
         ClientTransaction tx = (ClientTransaction)entry.getValue();
                 
         if (tx.getState() == ClientTransaction.TX_PREPARED)
         {           
            List dels = tx.getDeliveriesForSession(sessionId);
           
            if (!dels.isEmpty())
            {
               // There are outstanding prepared acks in this session
              
               return true;
            }
         }
      }
      return false;
   }
   
   // Protected ------------------------------------------------------------------------------------
  
   // Package Private ------------------------------------------------------------------------------
  
   Xid startTx(Xid xid) throws XAException
   {
      if (trace) { log.trace("starting " + xid); }
     
      ClientTransaction state = getTxInternal(xid);
     
      if (state != null)
      {
         throw new MessagingXAException(XAException.XAER_DUPID, "Transaction already exists with xid " + xid);
      }
           
      transactions.put(xid, new ClientTransaction());
     
      return xid;
   }
  
   void endTx(Xid xid, boolean success) throws XAException
   {
      if (trace) { log.trace("ending " + xid + ", success=" + success); }
       
      ClientTransaction state = getTxInternal(xid);
     
      if (state == null)
      { 
         throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + xid);
      }       
     
      state.setState(ClientTransaction.TX_ENDED);
   }
  
   int prepare(Xid xid, ConnectionDelegate connection) throws XAException
   {
      if (trace) { log.trace("preparing " + xid); }
     
      ClientTransaction state = getTxInternal(xid);
     
      if (state == null)
      {
         throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + xid);
      }
     
      checkAndRollbackXA(state, xid);
     
      TransactionRequest request =
         new TransactionRequest(TransactionRequest.TWO_PHASE_PREPARE_REQUEST, xid, state);
     
      sendTransactionXA(request, connection);     
     
      state.setState(ClientTransaction.TX_PREPARED);
     
      if (trace) { log.trace("State is now: " + state.getState()); }
     
      return XAResource.XA_OK;
   }
  
   void commit(Xid xid, boolean onePhase, ConnectionDelegate connection) throws XAException
   {
      if (trace) { log.trace("commiting xid " + xid + ", onePhase=" + onePhase); }
     
      ClientTransaction tx = removeTxInternal(xid);
     
      if (trace) { log.trace("got tx: " + tx + " state " + tx.getState()); }
         
      if (onePhase)
      {
         //Invalid xid
         if (tx == null)
         {      
            throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + xid);
         }
        
         checkAndRollbackXA(tx, xid);
        
         TransactionRequest request =
            new TransactionRequest(TransactionRequest.ONE_PHASE_COMMIT_REQUEST, null, tx);
        
         request.state = tx;   
        
         sendTransactionXA(request, connection);
      }
      else
      {
         if (tx != null)
         {
            if (tx.getState() != ClientTransaction.TX_PREPARED)
            {   
               throw new MessagingXAException(XAException.XAER_PROTO, "commit called for transaction, but it is not prepared");
            }
         }
         else
         {
            //It's possible we don't actually have the prepared tx here locally - this
            //may happen if we have recovered from failure and the transaction manager
            //is calling commit on the transaction as part of the recovery process.
         }
                          
         TransactionRequest request =
            new TransactionRequest(TransactionRequest.TWO_PHASE_COMMIT_REQUEST, xid, null);
        
         request.xid = xid;     
        
         sendTransactionXA(request, connection);       
      }
     
      if (tx != null)
      {
         tx.setState(ClientTransaction.TX_COMMITED);
      }
   }
     
   void rollback(Xid xid, ConnectionDelegate connection) throws XAException
   {
      if (trace) { log.trace("rolling back xid " + xid); }
     
      ClientTransaction tx = removeTxInternal(xid);
     
      if (tx == null)
      {
         throw new java.lang.IllegalStateException("Cannot find xid to remove " + xid);
      }
     
      //It's possible we don't actually have the prepared tx here locally - this
      //may happen if we have recovered from failure and the transaction manager
      //is calling rollback on the transaction as part of the recovery process.
     
      TransactionRequest request = null;
     
      //don't need the messages
      if (tx != null)
      {
         tx.clearMessages();
      }
            
      if ((tx == null) || tx.getState() == ClientTransaction.TX_PREPARED)
      {
         //2PC rollback
        
         request = new TransactionRequest(TransactionRequest.TWO_PHASE_ROLLBACK_REQUEST, xid, tx);
        
         if (trace) { log.trace("Sending rollback to server, tx:" + tx); }
                                 
         sendTransactionXA(request, connection);
      }
      else
      {
         //For one phase rollback there is nothing to do on the server
        
         if (tx == null)
         {    
            throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + xid);
         }
      }
                 
      //we redeliver the messages
      //locally to their original consumers if they are still open or cancel them to the server
      //if the original consumers have closed
     
      if (trace) { log.trace("Redelivering messages, tx:" + tx); }
     
      try
      {
         if (tx != null)
         {
            redeliverMessages(tx);
           
            tx.setState(ClientTransaction.TX_ROLLEDBACK)
         }
        
      }
      catch (JMSException e)
      {
         log.error("Failed to redeliver", e);
      }                              
   }
 
  
   Xid joinTx(Xid xid) throws XAException
   {
      if (trace) { log.trace("joining  " + xid); }
     
      ClientTransaction state = getTxInternal(xid);
     
      if (state == null)
      {        
         throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + xid);
      }
     
      return xid;
   }
  
  
  
   Xid resumeTx(Xid xid) throws XAException
   {
      if (trace) { log.trace("resuming " + xid); }
     
      ClientTransaction state = getTxInternal(xid);
     
      if (state == null)
      {      
         throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + xid);
      }
     
      return xid;
   }
  
   Xid suspendTx(Xid xid) throws XAException
   {
      if (trace) { log.trace("suspending " + xid); }

      ClientTransaction state = getTxInternal(xid);
     
      if (state == null)
      {      
         throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + xid);
      }
     
      return xid;
   }

   Xid convertTx(LocalTx localTx, Xid xid) throws XAException
   {
      if (trace) { log.trace("converting " + localTx + " to " + xid); }
     
      //Sanity check
     
      ClientTransaction newTx = getTxInternal(xid);

      if (newTx != null)
      {       
         throw new MessagingXAException(XAException.XAER_DUPID, "Transaction already exists:" + xid);
      }

      //Remove the local tx
     
      ClientTransaction local = removeTxInternal(localTx);

      if (local == null)
      {       
         throw new MessagingXAException(XAException.XAER_NOTA, "Cannot find transaction with xid:" + localTx);
      }
     
      // Add the local back in with the new xid
     
      transactions.put(xid, local);
     
      return xid;
   }
  
   Xid[] recover(int flags, ConnectionDelegate conn) throws XAException
   {
      if (trace) { log.trace("calling recover with flags: " + flags); }
     
      if (flags == XAResource.TMSTARTRSCAN)
      {
         try
         {
            Xid[] txs = conn.getPreparedTransactions();
           
            if (trace) { log.trace("Got " + txs.length + " transactions from server"); }
           
            //populate with TxState --MK
            for (int i = 0; i < txs.length;i++)
            {
               //Don't overwrite if it is already there
               if (!transactions.containsKey(txs[i]))
               {
                  ClientTransaction tx = new ClientTransaction();
  
                  tx.setState(ClientTransaction.TX_PREPARED);
  
                  transactions.put(txs[i], tx);
               }
            }

            return txs;
         }
         catch (JMSException e)
         {
            throw new MessagingXAException(XAException.XAER_RMFAIL, "Failed to get prepared transactions");
         }
      }
      else
      {
         return new Xid[0];
      }
   }
  
   // Private --------------------------------------------------------------------------------------
  
   private ClientTransaction getTxInternal(Object xid)
   {
      if (trace) { log.trace("getting transaction for " + xid); }
     
      return (ClientTransaction)transactions.get(xid);
   }
  
   private ClientTransaction removeTxInternal(Object xid)
   {
      return (ClientTransaction)transactions.remove(xid);
   }
  
   /*
    * Rollback has occurred so we need to redeliver any unacked messages corresponding to the acks
    * is in the transaction.
    *
    */
   private void redeliverMessages(ClientTransaction ts) throws JMSException
   {
      List sessionStates = ts.getSessionStates();
     
      //Need to do this in reverse order
     
      Collections.reverse(sessionStates);
     
      for (Iterator i = sessionStates.iterator(); i.hasNext();)
      {
         SessionTxState state = (SessionTxState)i.next();
        
         List acks = state.getAcks();
        
         if (!acks.isEmpty())
         {
            DeliveryInfo info = (DeliveryInfo)acks.get(0);
           
            MessageProxy mp = info.getMessageProxy();
           
            SessionDelegate del = mp.getSessionDelegate();
           
            del.redeliver(acks);
         }
      }
   }
  
   private synchronized LocalTx getNextTxId()
   {
      return new LocalTx();
   }
    
   private void sendTransactionXA(TransactionRequest request, ConnectionDelegate connection)
      throws XAException
   {
      try
      {
         connection.sendTransaction(request, false);
      }
      catch (Throwable t)
      {
         //Catch anything else
        
         //We assume that any error is recoverable - and the recovery manager should retry again
         //either after the network connection has been repaired (if that was the problem), or
         //the server has been fixed.
        
         //(In some cases it will not be possible to fix so the user will have to manually resolve the tx)
        
         //Therefore we throw XA_RETRY
         //Note we DO NOT throw XAER_RMFAIL or XAER_RMERR since both if these will cause the Arjuna
         //tx mgr NOT to retry and the transaction will have to be resolve manually.
        
         throw new MessagingXAException(XAException.XA_RETRY, "A Throwable was caught in sending the transaction", t);
      }
   }
  
   private void checkAndRollbackJMS(ClientTransaction state, Object xid) throws JMSException
   {
      Exception e = checkAndRollback(state, xid, false);
      if (e != null)
      {
         throw (JMSException)e;
      }
   }
  
   private void checkAndRollbackXA(ClientTransaction state, Object xid) throws XAException
   {
      Exception e = checkAndRollback(state, xid, true);
      if (e != null)
      {
         throw (XAException)e;
      }
   }
  
   private Exception checkAndRollback(ClientTransaction state, Object xid, boolean xa)
   {
      if (state.isFailedOver() && state.hasPersistentAcks())
      {
         // http://jira.jboss.org/jira/browse/JBMESSAGING-883
         // If a transaction has persistent acks in it and it has failed over from another server
         // then it's possible that on failover another consumer got the messages that we have already
         // received. Therfore to be strict and avoid any possibility of duplicate delivery we must
         // doom the transaction
         removeTx(xid);
        
         try
         {
            redeliverMessages(state);
         }
         catch (JMSException e)
         {
            log.error("Failed to redeliver messages", e);
         }
         
        
         final String msg = "Rolled back tx branch to avoid possibility of duplicates http://jira.jboss.org/jira/browse/JBMESSAGING-883";
        
         if (xa)
         {
            return new MessagingXAException(XAException.XA_HEURRB, msg);
         }
         else
         {
            return new MessagingTransactionRolledBackException(msg);
         }           
      }
     
      return null;
   }
  
   // Inner Classes --------------------------------------------------------------------------------
 
}
TOP

Related Classes of org.jboss.jms.tx.ResourceManager

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.