Package org.jboss.messaging.core.impl

Source Code of org.jboss.messaging.core.impl.MessagingQueue

/*
  * 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.messaging.core.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

import org.jboss.jms.server.MessagingTimeoutFactory;
import org.jboss.logging.Logger;
import org.jboss.messaging.core.contract.Delivery;
import org.jboss.messaging.core.contract.DeliveryObserver;
import org.jboss.messaging.core.contract.Distributor;
import org.jboss.messaging.core.contract.Filter;
import org.jboss.messaging.core.contract.Message;
import org.jboss.messaging.core.contract.MessageReference;
import org.jboss.messaging.core.contract.MessageStore;
import org.jboss.messaging.core.contract.PersistenceManager;
import org.jboss.messaging.core.contract.Queue;
import org.jboss.messaging.core.contract.Receiver;
import org.jboss.messaging.core.impl.clusterconnection.MessageSucker;
import org.jboss.messaging.core.impl.tx.Transaction;
import org.jboss.messaging.util.ConcurrentHashSet;
import org.jboss.util.timeout.Timeout;
import org.jboss.util.timeout.TimeoutTarget;

/**
*
* A MessagingQueue
*
* Can be used to implement a point to point queue, or a subscription fed from a topic
*
* @author <a href="mailto:ovidiu@feodorov.com">Ovidiu Feodorov</a>
* @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a>
* @version <tt>$Revision: 1295 $</tt>
*
* $Id: Queue.java 1295 2006-09-15 17:44:02Z timfox $
*
*/
public class MessagingQueue extends PagingChannelSupport implements Queue
{
   // Constants -----------------------------------------------------
 

   // Static --------------------------------------------------------
 
  private static final Logger log = Logger.getLogger(MessagingQueue.class);
 
   private static final long DEFAULT_RECOVER_DELIVERIES_TIMEOUT = 5 * 60 * 1000;
        
   // Attributes ----------------------------------------------------
  
   private int nodeID;
  
   protected String name;
  
   protected Filter filter;
  
   protected boolean clustered;
  
   protected Distributor remoteDistributor;
  
   protected Distributor localDistributor;

   private boolean trace = log.isTraceEnabled();
  
   private Set<MessageSucker> suckers;
  
   private boolean handleFlowControlForConsumers;
  
   private Map recoveryArea;
  
   private Map recoveryMap;
  
   private long recoverDeliveriesTimeout;
  
   // Constructors --------------------------------------------------
      
   public MessagingQueue(int nodeID, String name, long id, MessageStore ms, PersistenceManager pm,            
                         boolean recoverable, int maxSize, Filter filter,
                         int fullSize, int pageSize, int downCacheSize, boolean clustered,
                         long recoverDeliveriesTimeout)
   {
      super(id, ms, pm, recoverable, maxSize, fullSize, pageSize, downCacheSize);
           
      setup(nodeID, name, filter, clustered, recoverDeliveriesTimeout);
   }
  
   /** This constructor is used when loading queue from storage - the paging params, maxSize and recoverDeliveriesTimeout don't matter
    * they are injected when the queue or topic service is started
    */
   public MessagingQueue(int nodeID, String name, long id, MessageStore ms, PersistenceManager pm,            
                    boolean recoverable, Filter filter,
                    boolean clustered)
  {
    super(id, ms, pm, recoverable, -1, 100000, 2000, 2000); //paging params etc are actually ignored
   
    setup(nodeID, name, filter, clustered, DEFAULT_RECOVER_DELIVERIES_TIMEOUT);
  }
  
   /* This constructor is only used in tests - should we remove it? */
   public MessagingQueue(int nodeID, String name, long id, MessageStore ms, PersistenceManager pm,            
                       boolean recoverable, int maxSize, Filter filter, boolean clustered)
   {
     super(id, ms, pm, recoverable, maxSize);

     setup(nodeID, name, filter, clustered, DEFAULT_RECOVER_DELIVERIES_TIMEOUT);
   }
  
   /* Constructor for a remote queue representation in a cluster */
   public MessagingQueue(int nodeID, String name, long id, boolean recoverable,
                       Filter filter, boolean clustered)
   {
     super(id, null, null, recoverable, -1);
    
     setup(nodeID, name, filter, clustered, DEFAULT_RECOVER_DELIVERIES_TIMEOUT);    
   }

   public MessagingQueue(int nodeID, String name, long id, MessageStore ms, PersistenceManager pm,            
                         boolean recoverable, int maxSize, Filter filter,
                         int fullSize, int pageSize, int downCacheSize, boolean clustered,
                         long recoverDeliveriesTimeout, int deliveringCounterLevel)
   {
      this(nodeID, name, id, ms, pm, recoverable, maxSize, filter, fullSize, pageSize, downCacheSize, clustered,
           recoverDeliveriesTimeout);
      this.deliveringCount.setLevel(deliveringCounterLevel);
   }

   private void setup(int nodeID, String name, Filter filter, boolean clustered, long recoverDeliveriesTimeout)
   {
     this.nodeID = nodeID;

     this.name = name;

     this.filter = filter;

     this.clustered = clustered;
    
     this.recoverDeliveriesTimeout = recoverDeliveriesTimeout;
    
     localDistributor = new DistributorWrapper(new RoundRobinDistributor());
    
     remoteDistributor = new DistributorWrapper(new RoundRobinDistributor());
    
     distributor = new ClusterRoundRobinDistributor(localDistributor, remoteDistributor);
    
     suckers = new ConcurrentHashSet<MessageSucker>();
    
     recoveryArea = new ConcurrentReaderHashMap();
    
     recoveryMap = Collections.synchronizedMap(new LinkedHashMap());
   }
  
   // Queue implementation
   // ---------------------------------------------------------------
     
   public int getNodeID()
   {
     return nodeID;
   }
  
   public String getName()
   {
      return name;
   }
     
   public Filter getFilter()
   {
      return filter;
   }
  
   public boolean isClustered()
   {
      return clustered;
   }
  
   public Distributor getLocalDistributor()
   {
     return localDistributor;
   }
  
   public Distributor getRemoteDistributor()
   {
     return remoteDistributor;
   }
  
   public void staticMerge(Queue fromQueue) throws Exception
   {
      if (trace) { log.trace("Merging queue " + channelID + " into " + this +
                             " statically."); }
      pm.mergeChannelMessage(fromQueue.getChannelID(), channelID);
   }
  
   /**
    * Merge the contents of one queue with another - this happens at failover when a queue is failed
    * over to another node, but a queue with the same name already exists. In this case we merge the
    * two queues.
    */
   public void mergeIn(long theChannelID, int nodeID) throws Exception
   {
      if (trace) { log.trace("Merging queue " + channelID + " node id " + nodeID + " into " + this +
                           " initially refs:" + messageRefs.size()); }
          
      synchronized (lock)
      {
         flushDownCache();
        
         pm.claimMessagesInSuck(theChannelID);
                 
         PersistenceManager.InitialLoadInfo ili =
            pm.mergeAndLoad(theChannelID, this.channelID, fullSize - messageRefs.size(),
                            firstPagingOrder, nextPagingOrder);
           
         if (trace) { log.trace("Loaded " + ili.getRefInfos().size() + " refs"); }           

         doLoad(ili);        
        
         Map toRecover = (Map)recoveryArea.remove(new Integer(nodeID));
        
         if (trace) { log.trace("To recover is: " + toRecover); }
        
         LinkedList toTimeout = new LinkedList();
        
         if (toRecover != null)
         {          
            //TODO this can be optimised to avoid a second scan - we could do this in load
        
           if (trace) { log.trace("Recovery area is not empty, putting refs in recovery map"); }
          
           Iterator iter = messageRefs.iterator();
          
           while (iter.hasNext())
           {
             MessageReference ref = (MessageReference)iter.next();
            
             Message message = ref.getMessage();
            
             String sessionID = (String)toRecover.remove(new Long(message.getMessageID()));
            
             if (sessionID != null)
             {
               if (trace) { log.trace("Added ref " + ref + " to recovery map"); }
              
               RecoveryEntry re = new RecoveryEntry();
               re.ref = ref;
               re.sessionID = sessionID;
              
               recoveryMap.put(new Long(message.getMessageID()), re);
              
               deliveringCount.increment(ref.getMessage());
              
               iter.remove();
              
               toTimeout.addLast(ref);
             }
           }
                     
           //Note!
          //It is possible there are message ids in the toRecover that are not in the queue
          //This can happen if a delivery is replicated, the message delivered, then acked, then the node crashes
          //before the ack is replicated.         
          //This is ok
                     
           //Set up a timeout to put the refs back in the queue if they don't get claimed by failed over consumers
                                        
            MessagingTimeoutFactory.instance.getFactory().
              schedule(System.currentTimeMillis() + recoverDeliveriesTimeout, new ClearRecoveryMapTimeoutTarget(toTimeout));      
           
            if (trace) { log.trace("Set timeout to fire in " + recoverDeliveriesTimeout); }
         }
                          
         deliverInternal();
      }
   } 
  
   public List recoverDeliveries(List messageIds)
   {
     if (trace) { log.trace("Recovering deliveries"); }
    
     List refs = new ArrayList();
    
     Iterator iter = messageIds.iterator();
    
     while (iter.hasNext())
     {
       Long messageID = (Long)iter.next();
      
       RecoveryEntry re = (RecoveryEntry)recoveryMap.remove(messageID);
      
       //This can actually be null - e.g. if failure happened right after a successful Ack so the client doesn't
       //remove the delivery but is no longer on the server
       if (re != null)
       {               
         Delivery del = new SimpleDelivery(this, re.ref);
        
         if (trace) { log.trace("Recovered ref " + re.ref); }
        
         refs.add(del);
       }
     }
       
      return refs;
   }
  
   public void removeStrandedReferences(String sessionID)
   {
     if (trace) { log.trace("Removing stranded references for session " + sessionID); }
    
     // TODO this can be optimised - remove any remaining deliveries for the session id
     //these correspond to messages buffered on the client side for that client and should go back on the queue
    
      List toCancel = new ArrayList();

      //https://issues.jboss.org/browse/JBMESSAGING-1871
      synchronized (recoveryMap)
      {
         Iterator iter = recoveryMap.values().iterator();

         if (trace)
         {
            log.trace("Scanning recovery map for stray entries for session");
         }

         while (iter.hasNext())
         {
            RecoveryEntry re = (RecoveryEntry)iter.next();

            if (trace)
            {
               log.trace("Session id id " + re.sessionID);
            }

            if (re.sessionID.equals(sessionID))
            {
               MessageReference ref = re.ref;

               iter.remove();

               // Put back on queue

               toCancel.add(ref);
            }
         }
      }
    
     for (int i = toCancel.size() - 1; i >= 0; i--)
     {
       MessageReference ref = (MessageReference)toCancel.get(i);
      
       synchronized (lock)
      {
        messageRefs.addFirst(ref, ref.getMessage().getPriority());
       
        //Need to decrement the delivery count too
        deliveringCount.decrement(ref.getMessage());
      }
             
      if (trace) { log.trace("Found one, added back on queue"); }  
     }
    
   }
  
   public void registerSucker(MessageSucker sucker)
   {
     if (trace) { log.trace(this + " Registering sucker " + sucker); }
    
     synchronized (lock)
     {
       if (!suckers.contains(sucker))
       {
         suckers.add(sucker);
      
         handleFlowControlForConsumers = true;
        
         if (getReceiversReady() && localDistributor.getNumberOfReceivers() > 0)
         {          
           if (trace) { log.trace(this + " receivers ready so setting consumer to true"); }
        
           sucker.setConsuming(true);
         }
       }
     }
   }
  
   public boolean unregisterSucker(MessageSucker sucker)
   {
     synchronized (lock)
     {
       boolean removed = suckers.remove(sucker);
      
       if (removed && suckers.isEmpty())
       {
         handleFlowControlForConsumers = false;
       }
      
       return removed;
     }
   }
  
   public int getFullSize()
   {
     return fullSize;
   }
  
   public int getPageSize()
   {
     return pageSize;
   }
  
   public int getDownCacheSize()
   {
     return downCacheSize;
   }
  
   public void addToRecoveryArea(int nodeID, long messageID, String sessionID)
   {
     if (trace) { log.trace("Adding message id " + messageID + " to recovery area from node " + nodeID); }
    
     //Note. The add might find the id already there
      //This could happen after the failover node has moved
     //When the batch add happens, then an add comes in shortly after but has already been taken into account
    
     Integer nid = new Integer(nodeID);
    
     Map ids = (Map)recoveryArea.get(nid);
    
     if (ids == null)
     {
       ids = new ConcurrentHashMap();
      
       recoveryArea.put(nid, ids);
     }
    
     ids.put(new Long(messageID), sessionID);
   }
  
   public void removeFromRecoveryArea(int nodeID, long messageID)
   {
     if (trace) { log.trace("Removing message id " + messageID + " to recovery area from node " + nodeID); }
    
     Integer nid = new Integer(nodeID);
    
     Map ids = (Map)recoveryArea.get(nid);
    
     //The remove might fail to find the id
      //This can happen if the removal has already be done - this could happen after the failover node has moved
     //When the batch add happens, then an ack comes in shortly after but has already been taken into account

     if (ids != null && ids.remove(new Long(messageID)) != null)
     {
       if (ids.isEmpty())
        {
          recoveryArea.remove(nid);
        }
     }
   }
  
   public void removeAllFromRecoveryArea(int nodeID)
   {
     if (trace) { log.trace("Removing all from recovery area for node " + nodeID); }
    
     boolean removed = recoveryArea.remove(new Integer(nodeID)) != null;
      
     if (trace) { log.trace("Removed:" + removed); }
   }
  
   public void addAllToRecoveryArea(int nodeID, Map ids)
   {
     if (trace) { log.trace("Adding all from recovery area for node " + nodeID +" set " + ids); }
    
     Integer nid = new Integer(nodeID);
            
     if (!(ids instanceof ConcurrentHashMap))
     {
       ids = new ConcurrentHashMap(ids);
     }
    
     if (trace) { log.trace("Adding " + ids.size() + " ids to recovery area for node " + nodeID); }
    
     recoveryArea.put(nid, ids);
    
     if (trace) { log.trace("Added"); }
   }
  
   public long getRecoverDeliveriesTimeout()
   {
     return recoverDeliveriesTimeout;
   }
  
   public void load() throws Exception
   {
      super.load(clustered);
   }
  
   //testing only
  
   public Map getRecoveryArea()
   {
      if (trace) { log.trace("Getting recovery area, it is " + this.recoveryArea); }
     
     return this.recoveryArea;
   }
  
   public int getRecoveryMapSize()
   {
     return this.recoveryMap.size();
   }
  
   // ChannelSupport overrides --------------------------------------
  
   protected void deliverInternal()
  {
    super.deliverInternal();
   
    if (trace) { log.trace(this + " deliverInternal"); }
   
    if (handleFlowControlForConsumers && getReceiversReady() &&
       localDistributor.getNumberOfReceivers() > 0 && messageRefs.isEmpty())
    {
      if (trace) { log.trace("Informing suckers"); }
      //The receivers are still ready for more messages but there is nothing left in the local queue
      //so we inform the message suckers to start consuming (if they aren't already)
      informSuckers(true);
    }
  }
  
   protected synchronized void setReceiversReady(boolean receiversReady)
   {
     if (trace) { log.trace(this + " setReceiversReady " + receiversReady); }
    
     this.receiversReady = receiversReady;
    
     if (handleFlowControlForConsumers && receiversReady == false)
     {
       //No receivers are ready to accept message so tell the suckers to stop consuming
       informSuckers(false);
     }
   }
     
   // Public --------------------------------------------------------
  
   public String toString()
   {
      return "Queue[" + System.identityHashCode(this) + "/" + nodeID + "/" + channelID + "-" + name +  "]";
   }
  
   public boolean equals(Object other)
   {
     if (!(other instanceof MessagingQueue))
     {
       return false;
     }
    
     MessagingQueue queue = (MessagingQueue)other;
    
     return (this.nodeID == queue.nodeID && this.name.equals(queue.name));
   }

   // Package protected ---------------------------------------------
  
   // Protected -----------------------------------------------------
  
   // Private -------------------------------------------------------
  
   private void informSuckers(boolean consume)
   {
      Iterator<MessageSucker> iter = suckers.iterator();

      while (iter.hasNext())
      {
         MessageSucker sucker = iter.next();

         sucker.setConsuming(consume);
      }
   }
  
   // Inner classes -------------------------------------------------  
  
   protected class DistributorWrapper implements Distributor
   {
     private Distributor distributor;
    
     protected DistributorWrapper(Distributor distributor)
     {
       this.distributor = distributor;
     }

    public Delivery handle(DeliveryObserver observer, MessageReference reference, Transaction tx)
    {
      return distributor.handle(observer, reference, tx);
    }

    public synchronized boolean add(Receiver receiver)
    {
      if (trace) { log.trace(this + " attempting to add receiver " + receiver); }
        
        boolean added = distributor.add(receiver);
          
        if (trace) { log.trace("receiver " + receiver + (added ? "" : " NOT") + " added"); }
       
        setReceiversReady(true);
                   
        return added;       
    }

    public void clear()
    {
      distributor.clear();      
    }

    public boolean contains(Receiver receiver)
    {
        return distributor.contains(receiver);       
    }

    public int getNumberOfReceivers()
    {
        return distributor.getNumberOfReceivers();      
    }

    public Iterator iterator()
    {
      return distributor.iterator();      
    }

    public synchronized boolean remove(Receiver receiver)
    {    
       if (trace)
       {
          log.trace(MessagingQueue.this + " removing receiver " + receiver, new Exception ("trace"));
       }
        boolean removed = distributor.remove(receiver);
       
        if (trace)
        {
           log.trace(MessagingQueue.this + " removed = " + removed);
        }
       
        if (removed)
        {
          if (localDistributor.getNumberOfReceivers() == 0)
          {
             if (trace)
             {
                log.trace("Stopping suckers");
             }
            //Stop pulling from other queues into this one.
            informSuckers(false);
           
            if (remoteDistributor.getNumberOfReceivers() == 0)
            {
               setReceiversReady(false);        
            }
          }
        }        
 
        if (trace) { log.trace(this + (removed ? " removed " : " did NOT remove ") + receiver); }
 
        return removed;      
    }    
   }
  
   static class RecoveryEntry
   {
     String sessionID;
    
     MessageReference ref;
   }

  
   private class ClearRecoveryMapTimeoutTarget implements TimeoutTarget
   {
     private List ids;
    
     ClearRecoveryMapTimeoutTarget(List ids)
     {
       this.ids = ids;
     }
    
    public void timedOut(Timeout timeout)
    {
      if (trace) { log.trace("ClearRecoveryMap timeout fired"); }
     
      Iterator iter = ids.iterator();
     
      boolean added = false;
         
      while (iter.hasNext())
      {
        MessageReference ref = (MessageReference)iter.next();
       
        Object obj = recoveryMap.remove(new Long(ref.getMessage().getMessageID()));
       
        if (obj != null)
        {
          if (trace) { log.trace("Adding ref " + ref + " back into queue"); }
           
          synchronized (lock)
          {   
            messageRefs.addFirst(ref, ref.getMessage().getPriority());   
           
            deliveringCount.decrement(ref.getMessage());
          }         
         
          added = true;
        }
      }
     
      if (added)
      {
        synchronized (lock)
        {   
          deliverInternal();
       
      }
    }    
   }

   public void setClustered(boolean isClustered)
   {
      clustered = isClustered;
   }

   //only used by tests
   public DeliveringCounter getDeliveringCounter()
   {
      return deliveringCount;
   }
}
TOP

Related Classes of org.jboss.messaging.core.impl.MessagingQueue

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.