Package org.jboss.jms.server.endpoint

Source Code of org.jboss.jms.server.endpoint.ServerSessionEndpoint$DeliveryCallback

/*
  * 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.server.endpoint;


import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jms.IllegalStateException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;

import org.jboss.aop.AspectManager;
import org.jboss.jms.client.delegate.ClientBrowserDelegate;
import org.jboss.jms.client.delegate.ClientConsumerDelegate;
import org.jboss.jms.delegate.BrowserDelegate;
import org.jboss.jms.delegate.ConsumerDelegate;
import org.jboss.jms.destination.JBossDestination;
import org.jboss.jms.destination.JBossQueue;
import org.jboss.jms.destination.JBossTopic;
import org.jboss.jms.message.JBossMessage;
import org.jboss.jms.selector.Selector;
import org.jboss.jms.server.DestinationManager;
import org.jboss.jms.server.JMSCondition;
import org.jboss.jms.server.ServerPeer;
import org.jboss.jms.server.destination.ManagedDestination;
import org.jboss.jms.server.destination.ManagedQueue;
import org.jboss.jms.server.destination.ManagedTopic;
import org.jboss.jms.server.destination.TopicService;
import org.jboss.jms.server.endpoint.advised.BrowserAdvised;
import org.jboss.jms.server.endpoint.advised.ConsumerAdvised;
import org.jboss.jms.server.messagecounter.MessageCounter;
import org.jboss.jms.util.ExceptionUtil;
import org.jboss.jms.util.MessageQueueNameHelper;
import org.jboss.jms.wireformat.Dispatcher;
import org.jboss.logging.Logger;
import org.jboss.messaging.core.Channel;
import org.jboss.messaging.core.Delivery;
import org.jboss.messaging.core.DeliveryObserver;
import org.jboss.messaging.core.Queue;
import org.jboss.messaging.core.local.PagingFilteredQueue;
import org.jboss.messaging.core.message.MessageReference;
import org.jboss.messaging.core.plugin.IDManager;
import org.jboss.messaging.core.plugin.contract.ClusteredPostOffice;
import org.jboss.messaging.core.plugin.contract.MessageStore;
import org.jboss.messaging.core.plugin.contract.PersistenceManager;
import org.jboss.messaging.core.plugin.contract.PostOffice;
import org.jboss.messaging.core.plugin.postoffice.Binding;
import org.jboss.messaging.core.plugin.postoffice.cluster.LocalClusteredQueue;
import org.jboss.messaging.core.tx.Transaction;
import org.jboss.messaging.core.tx.TransactionException;
import org.jboss.messaging.core.tx.TransactionRepository;
import org.jboss.messaging.core.tx.TxCallback;
import org.jboss.util.id.GUID;

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

/**
* The server side representation of a JMS session.
*
* A user must not invoke methods of a session concurrently on different threads, however there are
* situations where multiple threads may access this object concurrently, for instance:
* - A session can be closed when it's connection is closed by the user which might be called on a
*   different thread.
* - A session can be closed when the server determines the connection is dead.
*
* If the session represents a connection consumer's session then the connection consumer will farm
* off messages to different sessions obtained from a pool, these may then cancel/ack etc on
* different threads, but the acks/cancels/etc will end up back here on the connection consumer
* session instance.
*
* @author <a href="mailto:ovidiu@jboss.org">Ovidiu Feodorov</a>
* @author <a href="mailto:tim.fox@jboss.com">Tim Fox</a>
* @author <a href="mailto:clebert.suconic@jboss.com">Clebert Suconic</a>
* @version <tt>$Revision: 2507 $</tt>
*
* $Id: ServerSessionEndpoint.java 2507 2007-02-28 23:46:51Z timfox $
*/
public class ServerSessionEndpoint implements SessionEndpoint
{
   // Constants ------------------------------------------------------------------------------------

   private static final Logger log = Logger.getLogger(ServerSessionEndpoint.class);
  
   static final String TEMP_QUEUE_MESSAGECOUNTER_PREFIX = "TempQueue.";
  
   public static final String JBOSS_MESSAGING_ORIG_DESTINATION = "JBM_ORIG_DESTINATION";

   public static final String JBOSS_MESSAGING_ORIG_MESSAGE_ID = "JBM_ORIG_MESSAGE_ID";
  
   public static final String JBOSS_MESSAGING_ACTUAL_EXPIRY_TIME = "JBM_ACTUAL_EXPIRY";
  
   // Static ---------------------------------------------------------------------------------------

   // Attributes -----------------------------------------------------------------------------------

   private boolean trace = log.isTraceEnabled();

   private int id;

   private volatile boolean closed;

   private ServerConnectionEndpoint connectionEndpoint;
  
   private ServerPeer sp;

   private Map consumers;
   private Map browsers;

   private PersistenceManager pm;
   private MessageStore ms;

   private DestinationManager dm;
   private IDManager idm;
   private TransactionRepository tr;
   private PostOffice postOffice;
   private int nodeId;
   private int maxDeliveryAttempts;
   private Queue defaultDLQ;
   private Queue defaultExpiryQueue;
  
   // Map <deliveryID, Delivery>
   private Map deliveries;
  
   private SynchronizedLong deliveryIdSequence;
  
   //Temporary until we have our own NIO transport  
   QueuedExecutor executor = new QueuedExecutor();
  
   // Constructors ---------------------------------------------------------------------------------

   ServerSessionEndpoint(int sessionID, ServerConnectionEndpoint connectionEndpoint)
      throws Exception
   {
      this.id = sessionID;

      this.connectionEndpoint = connectionEndpoint;

      sp = connectionEndpoint.getServerPeer();

      pm = sp.getPersistenceManagerInstance();
      ms = sp.getMessageStore();
      dm = sp.getDestinationManager();
      postOffice = sp.getPostOfficeInstance();    
      idm = sp.getChannelIDManager();
      nodeId = sp.getServerPeerID();
      tr = sp.getTxRepository();

      consumers = new HashMap();
    browsers = new HashMap();
     
      defaultDLQ = sp.getDefaultDLQInstance();
      defaultExpiryQueue = sp.getDefaultExpiryQueueInstance();
      tr = sp.getTxRepository();
      maxDeliveryAttempts = sp.getDefaultMaxDeliveryAttempts();
     
      deliveries = new ConcurrentHashMap();
     
      deliveryIdSequence = new SynchronizedLong(0);
   }
  
   // SessionDelegate implementation ---------------------------------------------------------------
      
   public ConsumerDelegate createConsumerDelegate(JBossDestination jmsDestination,
                                                  String selector,
                                                  boolean noLocal,
                                                  String subscriptionName,
                                                  boolean isCC) throws JMSException
   {
      try
      {
//         if (!connectionEndpoint.isFailoverConnection())
//         {
            // regular consumer
            return createConsumerDelegateInternal(jmsDestination, selector,
                                                  noLocal, subscriptionName);
  //       }

//         // we're child of a failover connection. Favor failover channels when creating new
//         // consumers
//         return createFailoverConsumerDelegateInternal(jmsDestination, selector,
//                                                       noLocal, subscriptionName,
//                                                       failoverChannelID);
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " createConsumerDelegate");
      }
   }
     
  public BrowserDelegate createBrowserDelegate(JBossDestination jmsDestination,
                                                String selector)
      throws JMSException
  {
      try
      {
//         if (!connectionEndpoint.isFailoverConnection())
//         {
            // regular browser
            return createBrowserDelegateInternal(jmsDestination, selector);
//         }
//
//         // we're child of a failover connection. Favor failover channels when creating new
//         // browsers
//         return createFailoverBrowserDelegateInternal(jmsDestination, selector, failoverChannelID);
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " createBrowserDelegate");
      }
  }

   public JBossQueue createQueue(String name) throws JMSException
   {
      try
      {
         if (closed)
         {
            throw new IllegalStateException("Session is closed");
         }
        
         ManagedDestination dest = (ManagedDestination)dm.getDestination(name, true);
        
         if (dest == null)
         {
            throw new JMSException("There is no administratively defined queue with name:" + name);
         }       
  
         return new JBossQueue(dest.getName());
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " createQueue");
      }
   }

   public JBossTopic createTopic(String name) throws JMSException
   {
      try
      {
         if (closed)
         {
            throw new IllegalStateException("Session is closed");
         }
        
         ManagedDestination dest = (ManagedDestination)dm.getDestination(name, false);
                 
         if (dest == null)
         {
            throw new JMSException("There is no administratively defined topic with name:" + name);
         }       
  
         return new JBossTopic(name);
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " createTopic");
      }
   }
  
   public void close() throws JMSException
   {
      try
      {
         localClose();
        
         connectionEndpoint.removeSession(id);
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " close");
      }
   }
     
   public long closing() throws JMSException
   {
      // currently does nothing
      if (trace) log.trace(this + " closing (noop)");
     
      return -1;
   }
   public void send(JBossMessage message, boolean checkForDuplicates) throws JMSException
   {
      try
      {               
         connectionEndpoint.sendMessage(message, null, checkForDuplicates);        
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " send");
      }
   }
  
   public void acknowledgeDelivery(Ack ack) throws JMSException
   {
      try
      {
         acknowledgeDeliveryInternal(ack);  
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " acknowledgeDelivery");
      }
   }    
        
   public void acknowledgeDeliveries(List acks) throws JMSException
   {   
      if (trace) {log.trace(this + " acknowledges deliveries " + acks); }
     
      try
      {
         Iterator iter = acks.iterator();
        
         while (iter.hasNext())
         {
            Ack ack = (Ack)iter.next();
           
            acknowledgeDeliveryInternal(ack);
         }
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " acknowledgeDeliveries");
      }
   }
            
   public void cancelDelivery(Cancel cancel) throws JMSException
   {
      if (trace) {log.trace(this + " cancelDelivery " + cancel); }
     
      try
      {
         Delivery del = cancelDeliveryInternal(cancel);
        
         //Prompt delivery
         promptDelivery((Channel)del.getObserver());
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " cancelDelivery");
      }    
   }           

   public void cancelDeliveries(List cancels) throws JMSException
   {
      if (trace) {log.trace(this + " cancels deliveries " + cancels); }
       
      try
      {
         // deliveries must be cancelled in reverse order

         Set channels = new HashSet();
                         
         for (int i = cancels.size() - 1; i >= 0; i--)
         {
            Cancel cancel = (Cancel)cancels.get(i);      
           
            if (trace) { log.trace(this + " cancelling delivery " + cancel.getDeliveryId()); }
                       
            Delivery del = cancelDeliveryInternal(cancel);
           
            channels.add(del.getObserver());
         }
                             
         // need to prompt delivery for all affected channels
        
         promptDelivery(channels);
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " cancelDeliveries");
      }
   }        
  
   public void recoverDeliveries(List deliveryRecoveryInfos) throws JMSException
   {
      if (trace) { log.trace(this + "recovers deliveries " + deliveryRecoveryInfos); }

      try
      {
         if (postOffice.isLocal())
         {
            throw new IllegalStateException("Recovering deliveries but post office is not clustered!");
         }
        
         long maxDeliveryId = 0;
                 
         //Sort into different list for each channel
         Map ackMap = new HashMap();
                 
         for (Iterator iter = deliveryRecoveryInfos.iterator(); iter.hasNext(); )
         {
            DeliveryRecovery deliveryInfo = (DeliveryRecovery)iter.next();
               
            String queueName = deliveryInfo.getQueueName();

            List acks = (List)ackMap.get(queueName);
           
            if (acks == null)
            {
               acks = new ArrayList();
              
               ackMap.put(queueName, acks);
            }
           
            acks.add(deliveryInfo);
        
        
         Iterator iter = ackMap.entrySet().iterator();
        
         while (iter.hasNext())
         {
            Map.Entry entry = (Map.Entry)iter.next();
           
            String queueName = (String)entry.getKey();
           
            //Look up channel
            Binding binding = postOffice.getBindingForQueueName(queueName);
           
            if (binding == null)
            {
               throw new IllegalStateException("Cannot find channel with queue name: " + queueName);
            }
           
            List acks = (List)entry.getValue();
           
            List ids = new ArrayList(acks.size());
           
            for (Iterator iter2 = acks.iterator(); iter2.hasNext(); )
            {
               DeliveryRecovery info = (DeliveryRecovery)iter2.next();
              
               ids.add(new Long(info.getMessageID()));
            }
           
            Queue queue = binding.getQueue();
           
            JMSCondition cond = (JMSCondition)binding.getCondition();                       
           
            ManagedDestination dest =
               sp.getDestinationManager().getDestination(cond.getName(), cond.isQueue());
           
            if (dest == null)
            {
               throw new IllegalStateException("Cannot find managed destination with name " +
                  cond.getName() + " isQueue" + cond.isQueue());
            }
           
            Queue dlqToUse =
               dest.getDLQ() == null ? defaultDLQ : dest.getDLQ();
           
            Queue expiryQueueToUse =
               dest.getExpiryQueue() == null ? defaultExpiryQueue : dest.getExpiryQueue();
           
            List dels = queue.recoverDeliveries(ids);
           
            Iterator iter2 = dels.iterator();
           
            Iterator iter3 = acks.iterator();
           
            while (iter2.hasNext())
            {
               Delivery del = (Delivery)iter2.next();
              
               DeliveryRecovery info = (DeliveryRecovery)iter3.next();
              
               long deliveryId = info.getDeliveryID();
              
               maxDeliveryId = Math.max(maxDeliveryId, deliveryId);
              
               if (trace) { log.trace(this + " Recovered delivery " + deliveryId + ", " + del); }
              
               deliveries.put(new Long(deliveryId),
                              new DeliveryRecord(del, -1, dlqToUse,
                                                 expiryQueueToUse, dest.getRedeliveryDelay()));
            }
         }
        
         this.deliveryIdSequence = new SynchronizedLong(maxDeliveryId + 1);
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " recoverDeliveries");
      }
   }
  
   public void addTemporaryDestination(JBossDestination dest) throws JMSException
   {
      try
      {
         if (closed)
         {
            throw new IllegalStateException("Session is closed");
         }
         if (!dest.isTemporary())
         {
            throw new InvalidDestinationException("Destination:" + dest +
               " is not a temporary destination");
         }

         connectionEndpoint.addTemporaryDestination(dest);
        
         // Register with the destination manager
        
         ManagedDestination mDest;
        
         int fullSize = connectionEndpoint.getDefaultTempQueueFullSize();
         int pageSize = connectionEndpoint.getDefaultTempQueuePageSize();
         int downCacheSize = connectionEndpoint.getDefaultTempQueueDownCacheSize();
        
         if (dest.isTopic())
         {
            mDest = new ManagedTopic(dest.getName(), fullSize, pageSize, downCacheSize);
         }
         else
         {
            mDest = new ManagedQueue(dest.getName(), fullSize, pageSize, downCacheSize);
         }
        
         dm.registerDestination(mDest);
        
         if (dest.isQueue())
         {           
            Queue coreQueue;

            coreQueue = new PagingFilteredQueue(dest.getName(),
                                                idm.getID(), ms, pm, true, false,
                                                -1, null, fullSize, pageSize, downCacheSize);

            String counterName = TEMP_QUEUE_MESSAGECOUNTER_PREFIX + dest.getName();
           
            MessageCounter counter =
               new MessageCounter(counterName, null, coreQueue, false, false,
                                  sp.getDefaultMessageCounterHistoryDayLimit());
           
            sp.getMessageCounterManager().registerMessageCounter(counterName, counter);
                                
            // make a binding for this queue
            postOffice.bindQueue(new JMSCondition(true, dest.getName()), coreQueue);
         }        
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " addTemporaryDestination");
      }
   }
  
   public void deleteTemporaryDestination(JBossDestination dest) throws JMSException
   {
      try
      {
         if (closed)
         {
            throw new IllegalStateException("Session is closed");
         }
  
         if (!dest.isTemporary())
         {
            throw new InvalidDestinationException("Destination:" + dest +
               " is not a temporary destination");
         }
        
         ManagedDestination mDest = dm.getDestination(dest.getName(), dest.isQueue());
        
         if (mDest == null)
         {
            throw new InvalidDestinationException("No such destination: " + dest);
         }
                 
         if (dest.isQueue())
         {
            //Unbind
            postOffice.unbindQueue(dest.getName());
           
            String counterName = TEMP_QUEUE_MESSAGECOUNTER_PREFIX + dest.getName();
           
            connectionEndpoint.removeTemporaryDestination(dest);
           
            MessageCounter counter =
               sp.getMessageCounterManager().unregisterMessageCounter(counterName);
           
            if (counter == null)
            {
               throw new IllegalStateException("Cannot find counter to unregister " + counterName);
            }
         }
         else
         {
            //Topic           
            Collection bindings =
               postOffice.getBindingsForCondition(new JMSCondition(false, dest.getName()));
           
            if (!bindings.isEmpty())
            {
               throw new IllegalStateException("Cannot delete temporary destination, " +
                  "since it has active consumer(s)");
            }
         }
        
         dm.unregisterDestination(mDest);                            
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " deleteTemporaryDestination");
      }
   }
  
   public void unsubscribe(String subscriptionName) throws JMSException
   {
      log.debug(this + " unsubscribing " + subscriptionName);
     
      try
      {
         if (closed)
         {
            throw new IllegalStateException("Session is closed");
         }
         if (subscriptionName == null)
         {
            throw new InvalidDestinationException("Destination is null");
         }
  
         String clientID = connectionEndpoint.getClientID();
  
         if (clientID == null)
         {
            throw new JMSException("null clientID on connection");
         }
        
         String queueName = MessageQueueNameHelper.createSubscriptionName(clientID, subscriptionName);
        
         Binding binding = postOffice.getBindingForQueueName(queueName);
        
         if (binding == null)
         {
            throw new InvalidDestinationException("Cannot find durable subscription with name " +
                                                  subscriptionName + " to unsubscribe");
         }
        
         // Section 6.11. JMS 1.1.
         // "It is erroneous for a client to delete a durable subscription while it has an active
         // TopicSubscriber for it or while a message received by it is part of a current
         // transaction or has not been acknowledged in the session."
        
         Queue sub = binding.getQueue();
        
         if (sub.getNumberOfReceivers() != 0)
         {
            throw new IllegalStateException("Cannot unsubscribe durable subscription " +
                                            subscriptionName + " since it has active subscribers");
         }
        
         //Look up the topic
        
         JMSCondition topicCond = (JMSCondition)binding.getCondition();
        
         String topicName = topicCond.getName();
        
         ManagedDestination mDest = dm.getDestination(topicName, false);
        
         //Unbind it
   
         if (mDest.isClustered() && !postOffice.isLocal())
         {
            ClusteredPostOffice cpo = (ClusteredPostOffice)postOffice;
           
            cpo.unbindClusteredQueue(queueName);
         }
         else
         {        
            postOffice.unbindQueue(queueName);
         }
        
         String counterName = TopicService.SUBSCRIPTION_MESSAGECOUNTER_PREFIX + sub.getName();
        
         MessageCounter counter = sp.getMessageCounterManager().unregisterMessageCounter(counterName);
        
         if (counter == null)
         {
            throw new IllegalStateException("Cannot find counter to remove " + counterName);
         }
      }
      catch (Throwable t)
      {
         throw ExceptionUtil.handleJMSInvocation(t, this + " unsubscribe");
      }
   }
  
   // Public ---------------------------------------------------------------------------------------
  
   public ServerConnectionEndpoint getConnectionEndpoint()
   {
      return connectionEndpoint;
   }

   public String toString()
   {
      return "SessionEndpoint[" + id + "]";
   }

   // Package protected ----------------------------------------------------------------------------
  
   void expireDelivery(Delivery del, Queue expiryQueue) throws Throwable
   {
      if (trace) { log.trace(this + " detected expired message " + del.getReference()); }
     
      if (expiryQueue != null)
      {
         if (trace) { log.trace(this + " sending expired message to expiry queue " + expiryQueue); }
        
         JBossMessage copy = makeCopyForDLQOrExpiry(true, del);
        
         moveInTransaction(copy, del, expiryQueue);
      }
      else
      {
         log.warn("No expiry queue has been configured so removing expired " +  del.getReference());
      }
   }
     
   void removeBrowser(int browserId) throws Exception
   {
      synchronized (browsers)
      {
         if (browsers.remove(new Integer(browserId)) == null)
         {
            throw new IllegalStateException("Cannot find browser with id " + browserId + " to remove");
         }
      }
   }
  
   void removeConsumer(int consumerId) throws Exception
   {
      synchronized (consumers)
      {
         if (consumers.remove(new Integer(consumerId)) == null)
         {
            throw new IllegalStateException("Cannot find consumer with id " + consumerId + " to remove");
         }
      }
   }
   
   void localClose() throws Throwable
   {
      if (closed)
      {
         throw new IllegalStateException("Session is already closed");
      }
     
      if (trace) log.trace(this + " close()");
           
      //We clone to avoid deadlock http://jira.jboss.org/jira/browse/JBMESSAGING-836
      Map consumersClone;
      synchronized (consumers)
      {
         consumersClone = new HashMap(consumers);
      }
     
      for( Iterator i = consumersClone.values().iterator(); i.hasNext(); )
      {
         ((ServerConsumerEndpoint)i.next()).localClose();
     
     
      consumers.clear();
     
     
      //We clone to avoid deadlock http://jira.jboss.org/jira/browse/JBMESSAGING-836
      Map browsersClone;
      synchronized (browsers)
      {
         browsersClone = new HashMap(browsers);
      }
     
      for( Iterator i = browsersClone.values().iterator(); i.hasNext(); )
      {
         ((ServerBrowserEndpoint)i.next()).localClose();
     
     
      browsers.clear();
     
     
      //Now cancel any remaining deliveries in reverse delivery order
      //Note we don't maintain order using a LinkedHashMap since then we lose
      //concurrency since we would have to lock it exclusively
     
      List entries = new ArrayList(deliveries.entrySet());
     
      //Sort them in reverse delivery id order
      Collections.sort(entries,
                       new Comparator()
                       {
                           public int compare(Object obj1, Object obj2)
                           {
                              Map.Entry entry1 = (Map.Entry)obj1;
                              Map.Entry entry2 = (Map.Entry)obj2;
                              Long id1 = (Long)entry1.getKey();
                              Long id2 = (Long)entry2.getKey();
                              return id2.compareTo(id1);
                           }
                       });

      Iterator iter = entries.iterator();
           
      Set channels = new HashSet();
     
      if (trace) { log.trace(this + " cancelling " + entries.size() + " deliveries"); }
     
      while (iter.hasNext())
      {
         Map.Entry entry = (Map.Entry)iter.next();
        
         if (trace) { log.trace(this + " cancelling delivery with delivery id: " + entry.getKey()); }
        
         DeliveryRecord rec = (DeliveryRecord)entry.getValue();
        
         rec.del.cancel();
        
         channels.add(rec.del.getObserver());
      }
     
      promptDelivery(channels);
     
      deliveries.clear();
     
      sp.removeSession(new Integer(id));
           
      Dispatcher.instance.unregisterTarget(id, this);
     
      closed = true;
   }           
  
   void cancelDelivery(long deliveryId) throws Throwable
   {
      DeliveryRecord rec = (DeliveryRecord)deliveries.remove(new Long(deliveryId));
     
      if (rec == null)
      {
         throw new IllegalStateException("Cannot find delivery to cancel " + deliveryId);
      }
     
      rec.del.cancel();
   }
  
   long addDelivery(Delivery del, int consumerId, Queue dlq, Queue expiryQueue, long redeliveryDelay)
   {
      long deliveryId = deliveryIdSequence.increment();
     
      deliveries.put(new Long(deliveryId), new DeliveryRecord(del, consumerId, dlq, expiryQueue, redeliveryDelay));
     
      if (trace) { log.trace(this + " added delivery " + deliveryId + ": " + del); }
     
      return deliveryId;     
   }     
  
   void acknowledgeTransactionally(List acks, Transaction tx) throws Throwable
   {
      if (trace) { log.trace(this + " acknowledging transactionally " + acks.size() + " messages for " + tx); }
     
      DeliveryCallback deliveryCallback = (DeliveryCallback)tx.getCallback(this);
     
      if (deliveryCallback == null)
      {
         deliveryCallback = new DeliveryCallback();
         tx.addCallback(deliveryCallback, this);
      }
           
      for(Iterator i = acks.iterator(); i.hasNext(); )
      {
         Ack ack = (Ack)i.next();
         Long id = new Long(ack.getDeliveryID());
         DeliveryRecord rec = (DeliveryRecord)deliveries.get(id);
        
         if (rec == null)
         {
            log.warn("Cannot find delivery to acknowledge " + ack);
            continue;
         }
                          
         deliveryCallback.addDeliveryId(id);
         rec.del.acknowledge(tx);
      }     
   }
  
   /**
    * Starts this session's Consumers
    */
   void setStarted(boolean s) throws Throwable
   {
      //We clone to prevent deadlock http://jira.jboss.org/jira/browse/JBMESSAGING-836
      Map consumersClone;
      synchronized(consumers)
      {
         consumersClone = new HashMap(consumers);
      }
     
      for(Iterator i = consumersClone.values().iterator(); i.hasNext(); )
      {
         ServerConsumerEndpoint sce = (ServerConsumerEndpoint)i.next();
         if (s)
         {
            sce.start();
         }
         else
         {
            sce.stop();
         }
      }     
   }
  
   void promptDelivery(final Channel channel)
   {
      try
      {
         //Prompting delivery must be asynchronous to avoid deadlock
         //but we cannot use one way invocations on cancelDelivery and
         //cancelDeliveries because remoting one way invocations can
         //overtake each other in flight - this problem will
         //go away when we have our own transport and our dedicated connection
         this.executor.execute(new Runnable() { public void run() { channel.deliver(); } } );
      }
      catch (Throwable t)
      {
         log.error("Failed to prompt delivery", t);
      }
   }
  

   // Protected ------------------------------------------------------------------------------------

   // Private --------------------------------------------------------------------------------------
  
   private Delivery cancelDeliveryInternal(Cancel cancel) throws Throwable
   {
      DeliveryRecord rec = (DeliveryRecord)deliveries.remove(new Long(cancel.getDeliveryId()));
     
      if (rec == null)
      {
         throw new IllegalStateException("Cannot find delivery to cancel " + cancel.getDeliveryId());
      }
                
      //Note we check the flag *and* evaluate again, this is because the server and client clocks may
      //be out of synch and don't want to send back to the client a message it thought it has sent to
      //the expiry queue 
      boolean expired = cancel.isExpired() || rec.del.getReference().getMessage().isExpired();
     
      //Note we check the flag *and* evaluate again, this is because the server value of maxDeliveries
      //might get changed after the client has sent the cancel - and we don't want to end up cancelling
      //back to the original queue
      boolean reachedMaxDeliveryAttempts =
         cancel.isReachedMaxDeliveryAttempts() || cancel.getDeliveryCount() >= maxDeliveryAttempts;
                   
      Delivery del = rec.del;  
        
      if (!expired && !reachedMaxDeliveryAttempts)
      {
         //Normal cancel back to the queue
        
         del.getReference().setDeliveryCount(cancel.getDeliveryCount());
        
         //Do we need to set a redelivery delay?
        
         if (rec.redeliveryDelay != 0)
         {
            del.getReference().setScheduledDeliveryTime(System.currentTimeMillis() + rec.redeliveryDelay);
         }
        
         del.cancel();
      }
      else
      {                 
         if (expired)
         {
            //Sent to expiry queue
           
            JBossMessage copy = makeCopyForDLQOrExpiry(true, del);
           
            moveInTransaction(copy, del, rec.expiryQueue);
         }
         else
         {
            //Send to DLQ
           
            JBossMessage copy = makeCopyForDLQOrExpiry(false, del);
           
            moveInTransaction(copy, del, rec.dlq);
         }
      }     
     
      return rec.del;
   }     
  
   private JBossMessage makeCopyForDLQOrExpiry(boolean expiry, Delivery del) throws Exception
   {
      //We copy the message and send that to the dlq/expiry queue - this is because
      //otherwise we may end up with a ref with the same message id in the queue more than once
      //which would barf - this might happen if the same message had been expire from multiple
      //subscriptions of a topic for example
      //We set headers that hold the original message destination, expiry time and original message id
     
      JBossMessage msg = ((JBossMessage)del.getReference().getMessage());
     
      JBossMessage copy = msg.doCopy();
     
      long newMessageId = sp.getMessageIDManager().getID();
     
      copy.setMessageId(newMessageId);
     
      //reset expiry
      copy.setExpiration(0);
     
      String origMessageId = msg.getJMSMessageID();
     
      String origDest = msg.getJMSDestination().toString();
           
      copy.setStringProperty(JBOSS_MESSAGING_ORIG_MESSAGE_ID, origMessageId);
     
      copy.setStringProperty(JBOSS_MESSAGING_ORIG_DESTINATION, origDest);
     
      if (expiry)
      {
         long actualExpiryTime = System.currentTimeMillis();
        
         copy.setLongProperty(JBOSS_MESSAGING_ACTUAL_EXPIRY_TIME, actualExpiryTime);
      }
     
      return copy;
   }
  
   private void moveInTransaction(JBossMessage msg, Delivery del, Queue queue) throws Throwable
   {
      Transaction tx = tr.createTransaction();
     
      MessageReference ref = ms.reference(msg);
                      
      try
      {              
         if (queue != null)
         {                                                      
            queue.handle(null, ref, tx);
            del.acknowledge(tx);
         }
         else
         {
            log.warn("Cannot move to destination since destination has not been deployed! " +
               "The message will be removed");
           
            del.acknowledge(tx);
         }            
        
         tx.commit();        
      }
      catch (Throwable t)
      {
         tx.rollback();
         throw t;
      }
      finally
      {
         if (ref != null)
         {
            ref.releaseMemoryReference();
         }
      }
     
      //Need to prompt delivery on the dlq/expiry queue
     
      //TODO - are we sure this is the right place to prompt delivery?
      if (queue != null)
      {
         promptDelivery(queue);
      }
   }
  
   private void acknowledgeDeliveryInternal(Ack ack) throws Throwable
   {
      if (trace) { log.trace(this + " acknowledging delivery " + ack); }
     
      DeliveryRecord rec = (DeliveryRecord)deliveries.remove(new Long(ack.getDeliveryID()));
     
      if (rec == null)
      {
         log.warn("Cannot find " + ack + " to acknowledge, " +
            "maybe it was already acknowledged before failover!");
         return;
      }
     
      rec.del.acknowledge(null);   
     
      if (trace) { log.trace(this + " acknowledged delivery " + ack); }
   }

   private ConsumerDelegate createConsumerDelegateInternal(JBossDestination jmsDestination,
                                                           String selectorString,
                                                           boolean noLocal,
                                                           String subscriptionName)
      throws Throwable
   {
      if (closed)
      {
         throw new IllegalStateException("Session is closed");
      }
     
      if ("".equals(selectorString))
      {
         selectorString = null;
      }
     
      if (trace)
      {
         log.trace(this + " creating consumer for " + jmsDestination +
            (selectorString == null ? "" : ", selector '" + selectorString + "'") +
            (subscriptionName == null ? "" : ", subscription '" + subscriptionName + "'") +
            (noLocal ? ", noLocal" : ""));
      }

      ManagedDestination mDest = dm.
         getDestination(jmsDestination.getName(), jmsDestination.isQueue());
     
      if (mDest == null)
      {
         throw new InvalidDestinationException("No such destination: " + jmsDestination);
      }
     
      if (jmsDestination.isTemporary())
      {
         // Can only create a consumer for a temporary destination on the same connection
         // that created it
         if (!connectionEndpoint.hasTemporaryDestination(jmsDestination))
         {
            String msg = "Cannot create a message consumer on a different connection " +
                         "to that which created the temporary destination";
            throw new IllegalStateException(msg);
         }
      }
     
      int consumerID = connectionEndpoint.getServerPeer().getNextObjectID();
     
      Binding binding = null;
     
      // Always validate the selector first
      Selector selector = null;
      if (selectorString != null)
      {
         selector = new Selector(selectorString);
      }
     
      if (jmsDestination.isTopic())
      {
         JMSCondition topicCond = new JMSCondition(false, jmsDestination.getName());
        
         if (subscriptionName == null)
         {
            // non-durable subscription
            if (log.isTraceEnabled()) { log.trace(this + " creating new non-durable subscription on " + jmsDestination); }
           
            // Create the non durable sub
                       
            PagingFilteredQueue q;
           
            if (postOffice.isLocal() || !mDest.isClustered())
            {
               q = new PagingFilteredQueue(new GUID().toString(), idm.getID(), ms, pm, true, false,
                        mDest.getMaxSize(), selector,
                        mDest.getFullSize(),
                        mDest.getPageSize(),
                        mDest.getDownCacheSize());
              
               binding = postOffice.bindQueue(topicCond, q);
            }
            else
           
               q = new LocalClusteredQueue((ClusteredPostOffice)postOffice, nodeId, new GUID().toString(),
                                           idm.getID(), ms, pm, true, false,
                                           mDest.getMaxSize(), selector, tr,
                                           mDest.getFullSize(),
                                           mDest.getPageSize(),
                                           mDest.getDownCacheSize());
              
               ClusteredPostOffice cpo = (ClusteredPostOffice)postOffice;

               binding = cpo.bindClusteredQueue(topicCond, (LocalClusteredQueue)q);
            }
            String counterName = TopicService.SUBSCRIPTION_MESSAGECOUNTER_PREFIX + q.getName();
 
            int dayLimitToUse = mDest.getMessageCounterHistoryDayLimit();
            if (dayLimitToUse == -1)
            {
               //Use override on server peer
               dayLimitToUse = sp.getDefaultMessageCounterHistoryDayLimit();
            }
           
            MessageCounter counter =
               new MessageCounter(counterName, null, q, true, false,
                                  dayLimitToUse);
           
            sp.getMessageCounterManager().registerMessageCounter(counterName, counter);
         }
         else
         {
            if (jmsDestination.isTemporary())
            {
               throw new InvalidDestinationException("Cannot create a durable subscription on a temporary topic");
            }
           
            // We have a durable subscription, look it up
            String clientID = connectionEndpoint.getClientID();
            if (clientID == null)
            {
               throw new JMSException("Cannot create durable subscriber without a valid client ID");
            }
           
            // See if there any bindings with the same client_id.subscription_name name
           
            String name = MessageQueueNameHelper.createSubscriptionName(clientID, subscriptionName);
           
            binding = postOffice.getBindingForQueueName(name);
           
            if (binding == null)
            {
               // Does not already exist
              
               if (trace) { log.trace(this + " creating new durable subscription on " + jmsDestination); }
                             
               PagingFilteredQueue q;

               if (postOffice.isLocal())
               {
                  q = new PagingFilteredQueue(name, idm.getID(), ms, pm, true, true,
                                              mDest.getMaxSize(), selector,
                                              mDest.getFullSize(),
                                              mDest.getPageSize(),
                                              mDest.getDownCacheSize());

                  binding = postOffice.bindQueue(topicCond, q);
               }
               else
               {
                  q = new LocalClusteredQueue((ClusteredPostOffice)postOffice, nodeId, name, idm.getID(),
                                              ms, pm, true, true,
                                              mDest.getMaxSize(), selector, tr,
                                              mDest.getFullSize(),
                                              mDest.getPageSize(),
                                              mDest.getDownCacheSize());
                 
                  ClusteredPostOffice cpo = (ClusteredPostOffice)postOffice;
                 
                  if (mDest.isClustered())
                  {
                     binding = cpo.bindClusteredQueue(topicCond, (LocalClusteredQueue)q);
                  }
                  else
                  {
                     binding = cpo.bindQueue(topicCond, q);
                  }
               }
               String counterName = TopicService.SUBSCRIPTION_MESSAGECOUNTER_PREFIX + q.getName();
                      
               MessageCounter counter =
                  new MessageCounter(counterName, subscriptionName, q, true, true,
                                     mDest.getMessageCounterHistoryDayLimit());
              
               sp.getMessageCounterManager().registerMessageCounter(counterName, counter);
            }
            else
            {
               //Durable sub already exists
              
               if (trace) { log.trace(this + " subscription " + subscriptionName + " already exists"); }
              
               // From javax.jms.Session Javadoc (and also JMS 1.1 6.11.1):
               // A client can change an existing durable subscription by creating a durable
               // TopicSubscriber with the same name and a new topic and/or message selector.
               // Changing a durable subscriber is equivalent to unsubscribing (deleting) the old
               // one and creating a new one.
              
               String filterString =
                  binding.getQueue().getFilter() != null ?
                     binding.getQueue().getFilter().getFilterString() : null;
              
               boolean selectorChanged =
                  (selectorString == null && filterString != null) ||
                  (filterString == null && selectorString != null) ||
                  (filterString != null && selectorString != null &&
                           !filterString.equals(selectorString));
              
               if (trace) { log.trace("selector " + (selectorChanged ? "has" : "has NOT") + " changed"); }
              
               JMSCondition cond = (JMSCondition)binding.getCondition();
              
               boolean topicChanged = !cond.getName().equals(jmsDestination.getName());
              
               if (log.isTraceEnabled()) { log.trace("topic " + (topicChanged ? "has" : "has NOT") + " changed"); }
              
               if (selectorChanged || topicChanged)
               {
                  if (trace) { log.trace("topic or selector changed so deleting old subscription"); }
                 
                  // Unbind the durable subscription
                 
                  if (mDest.isClustered() && !postOffice.isLocal())
                  {
                     ClusteredPostOffice cpo = (ClusteredPostOffice)postOffice;
                    
                     cpo.unbindClusteredQueue(name);
                  }
                  else
                  {
                     postOffice.unbindQueue(name);
                  }
                 
                  // create a fresh new subscription
                                   
                  PagingFilteredQueue q;
                 
                  if (postOffice.isLocal())
                  {
                     q = new PagingFilteredQueue(name, idm.getID(), ms, pm, true, true,
                              mDest.getMaxSize(), selector,
                              mDest.getFullSize(),
                              mDest.getPageSize(),
                              mDest.getDownCacheSize());
                     binding = postOffice.bindQueue(topicCond, q);
                  }
                  else
                  {
                     q = new LocalClusteredQueue((ClusteredPostOffice)postOffice, nodeId, name, idm.getID(), ms, pm, true, true,
                              mDest.getMaxSize(), selector, tr,
                              mDest.getFullSize(),
                              mDest.getPageSize(),
                              mDest.getDownCacheSize());
                    
                     ClusteredPostOffice cpo = (ClusteredPostOffice)postOffice;
                    
                     if (mDest.isClustered())
                     {
                        binding = cpo.bindClusteredQueue(topicCond, (LocalClusteredQueue)q);
                     }
                     else
                     {
                        binding = cpo.bindQueue(topicCond, (LocalClusteredQueue)q);
                     }
                  }
                  String counterName = TopicService.SUBSCRIPTION_MESSAGECOUNTER_PREFIX + q.getName();
                 
                  MessageCounter counter =
                     new MessageCounter(counterName, subscriptionName, q, true, true,
                                        mDest.getMessageCounterHistoryDayLimit());
                 
                  sp.getMessageCounterManager().registerMessageCounter(counterName, counter);
               }
            }
         }
      }
      else
      {
         // Consumer on a jms queue
        
         // Let's find the binding
         binding = postOffice.getBindingForQueueName(jmsDestination.getName());
        
         if (binding == null)
         {
            throw new IllegalStateException("Cannot find binding for jms queue: " + jmsDestination.getName());
         }
      }
     
      int prefetchSize = connectionEndpoint.getPrefetchSize();
     
      Queue dlqToUse = mDest.getDLQ() == null ? defaultDLQ : mDest.getDLQ();
     
      Queue expiryQueueToUse = mDest.getExpiryQueue() == null ? defaultExpiryQueue : mDest.getExpiryQueue();
     
      long redeliveryDelay = mDest.getRedeliveryDelay();
     
      if (redeliveryDelay == 0)
      {
         redeliveryDelay = sp.getDefaultRedeliveryDelay();
      }
     
      ServerConsumerEndpoint ep =
         new ServerConsumerEndpoint(consumerID, (PagingFilteredQueue)binding.getQueue(),
                  binding.getQueue().getName(), this, selectorString, noLocal,
                  jmsDestination, dlqToUse, expiryQueueToUse, redeliveryDelay);
     
      ConsumerAdvised advised;
     
      // Need to synchronized to prevent a deadlock
      // See http://jira.jboss.com/jira/browse/JBMESSAGING-797
      synchronized (AspectManager.instance())
      {      
         advised = new ConsumerAdvised(ep);
      }
     
      Dispatcher.instance.registerTarget(consumerID, advised);
     
      ClientConsumerDelegate stub =
         new ClientConsumerDelegate(consumerID,
                                    prefetchSize, maxDeliveryAttempts);
     
      synchronized (consumers)
      {
         consumers.put(new Integer(consumerID), ep);
      }
        
      log.debug(this + " created and registered " + ep);
     
      return stub;
   }  

   private BrowserDelegate createBrowserDelegateInternal(JBossDestination jmsDestination,
                                                         String selector) throws Throwable
   {
      if (closed)
      {
         throw new IllegalStateException("Session is closed");
      }

      if (jmsDestination == null)
      {
         throw new InvalidDestinationException("null destination");
      }

      if (jmsDestination.isTopic())
      {
         throw new IllegalStateException("Cannot browse a topic");
      }

      if (dm.getDestination(jmsDestination.getName(), jmsDestination.isQueue()) == null)
      {
         throw new InvalidDestinationException("No such destination: " + jmsDestination);
      }

      log.debug(this + " creating browser for " + jmsDestination +
         (selector == null ? "" : ", selector '" + selector + "'"));

      Binding binding = postOffice.getBindingForQueueName(jmsDestination.getName()); // TODO

      int browserID = connectionEndpoint.getServerPeer().getNextObjectID();

      ServerBrowserEndpoint ep =
         new ServerBrowserEndpoint(this, browserID,
                                   (PagingFilteredQueue)binding.getQueue(), selector);

      // still need to synchronized since close() can come in on a different thread
      synchronized (browsers)
      {
         browsers.put(new Integer(browserID), ep);
      }

      BrowserAdvised advised;
     
      // Need to synchronized to prevent a deadlock
      // See http://jira.jboss.com/jira/browse/JBMESSAGING-797
      synchronized (AspectManager.instance())
      {      
         advised = new BrowserAdvised(ep);
      }
     
      Dispatcher.instance.registerTarget(browserID, advised);

      ClientBrowserDelegate stub = new ClientBrowserDelegate(browserID);

      log.debug(this + " created and registered " + ep);

      return stub;
   }

   private void promptDelivery(Set channels)
   {
      //Now prompt delivery on the channels
      Iterator iter = channels.iterator();
     
      while (iter.hasNext())
      {
         DeliveryObserver observer = (DeliveryObserver)iter.next();
        
         promptDelivery((Channel)observer);
      }
   }
  
   // Inner classes --------------------------------------------------------------------------------
  
   /*
    * Holds a record of a delivery - we need to store the consumer id as well
    * hence this class
    * We can't rely on the cancel being driven from the MessageCallbackHandler since
    * the deliveries may have got lost in transit (ignored) since the consumer might have closed
    * when they were in transit.
    * In such a case we might otherwise end up with the consumer closing but not all it's deliveries being
    * cancelled, which would mean they wouldn't be cancelled until the session is closed which is too late
    *
    * We need to store various pieces of information, such as consumer id, dlq, expiry queue
    * since we need this at cancel time, but by then the actual consumer might have closed
    */
   private static class DeliveryRecord
   {
      Delivery del;
     
      int consumerId;
     
      Queue dlq;
     
      Queue expiryQueue;
     
      long redeliveryDelay;
     
      DeliveryRecord(Delivery del, int consumerId, Queue dlq, Queue expiryQueue, long redeliveryDelay)
      {
         this.del = del;
        
         this.consumerId = consumerId;
        
         this.dlq = dlq;
        
         this.expiryQueue = expiryQueue;
        
         this.redeliveryDelay = redeliveryDelay;
      }           
   }
  
   /**
    *
    * The purpose of this class is to remove deliveries from the delivery list on commit
    * Each transaction has once instance of this per SCE
    *
    */
   private class DeliveryCallback implements TxCallback
   {
      List delList = new ArrayList();
        
      public void beforePrepare()
      {        
         //NOOP
      }
     
      public void beforeCommit(boolean onePhase)
      {        
         //NOOP
      }
     
      public void beforeRollback(boolean onePhase)
      {        
         //NOOP
      }
     
      public void afterPrepare()
      {        
         //NOOP
      }
     
      public synchronized void afterCommit(boolean onePhase) throws TransactionException
      {
         // Remove the deliveries from the delivery map.
         Iterator iter = delList.iterator();
         while (iter.hasNext())
         {
            Long deliveryId = (Long)iter.next();
           
            deliveries.remove(deliveryId);
         }
      }
     
      public void afterRollback(boolean onePhase) throws TransactionException
      {                           
         //One phase rollbacks never hit the server - they are dealt with locally only
         //so this would only ever be executed for a two phase rollback.

         //We don't do anything since cancellation is driven from the client.
      }
     
      synchronized void addDeliveryId(Long deliveryId)
      {
         delList.add(deliveryId);
      }
   }
}
TOP

Related Classes of org.jboss.jms.server.endpoint.ServerSessionEndpoint$DeliveryCallback

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.