Package net.timewalker.ffmq3.local.session

Source Code of net.timewalker.ffmq3.local.session.LocalSession

/*
* This file is part of FFMQ.
*
* FFMQ 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 of the License, or
* (at your option) any later version.
*
* FFMQ 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 FFMQ; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/
package net.timewalker.ffmq3.local.session;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import javax.jms.Destination;
import javax.jms.IllegalStateException;
import javax.jms.InvalidDestinationException;
import javax.jms.JMSException;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.Session;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.Topic;
import javax.jms.TopicSubscriber;

import net.timewalker.ffmq3.FFMQConstants;
import net.timewalker.ffmq3.FFMQException;
import net.timewalker.ffmq3.common.destination.TemporaryQueueRef;
import net.timewalker.ffmq3.common.destination.TemporaryTopicRef;
import net.timewalker.ffmq3.common.message.AbstractMessage;
import net.timewalker.ffmq3.common.session.AbstractSession;
import net.timewalker.ffmq3.local.FFMQEngine;
import net.timewalker.ffmq3.local.MessageLock;
import net.timewalker.ffmq3.local.MessageLockSet;
import net.timewalker.ffmq3.local.TransactionItem;
import net.timewalker.ffmq3.local.TransactionSet;
import net.timewalker.ffmq3.local.connection.LocalConnection;
import net.timewalker.ffmq3.local.destination.LocalQueue;
import net.timewalker.ffmq3.local.destination.LocalTopic;
import net.timewalker.ffmq3.local.destination.notification.NotificationProxy;
import net.timewalker.ffmq3.security.Action;
import net.timewalker.ffmq3.security.Resource;
import net.timewalker.ffmq3.utils.Committable;
import net.timewalker.ffmq3.utils.ErrorTools;
import net.timewalker.ffmq3.utils.StringTools;
import net.timewalker.ffmq3.utils.concurrent.SynchronizationBarrier;
import net.timewalker.ffmq3.utils.id.IntegerID;
import net.timewalker.ffmq3.utils.id.UUIDProvider;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
* <p>Implementation of a local JMS {@link Session}</p>
*/
public class LocalSession extends AbstractSession
{
    private static final Log log = LogFactory.getLog(LocalSession.class);

    // Attributes
    protected FFMQEngine engine;
   
    // Runtime
    private List pendingQueuePuts = new Vector();
    private List pendingTopicPuts = new Vector();
    private TransactionSet transactionSet = new TransactionSet();
    private boolean debugEnabled = log.isDebugEnabled();
   
    // For internal use by the remote layer
    protected NotificationProxy notificationProxy;
   
    // Message stats
    private long consumedCount;
    private long producedCount;
   
    /**
     * Constructor
     */
    public LocalSession( IntegerID id , LocalConnection connection , FFMQEngine engine , boolean transacted , int acknowlegdeMode )
    {
        super(id,connection,transacted,acknowlegdeMode);
        this.engine = engine;
    }
   
    /**
   * @param notificationProxy the notificationProxy to set
   */
  public final void setNotificationProxy(NotificationProxy notificationProxy)
  {
    this.notificationProxy = notificationProxy;
  }
   
  /**
   * @return the notificationProxy
   */
  public final NotificationProxy getNotificationProxy()
  {
    return notificationProxy;
  }
   
    /**
     * Called from producers when sending a message
     * @param message message to dispatch
     * @throws JMSException
     */
    public final void dispatch( AbstractMessage message ) throws JMSException
    {
        // Security
        LocalConnection conn = (LocalConnection)getConnection();
        if (conn.isSecurityEnabled())
        {
            Destination destination = message.getJMSDestination();
            if (destination instanceof Queue)
            {
                String queueName = ((Queue)destination).getQueueName();
                if (conn.isRegisteredTemporaryQueue(queueName))
                {
                    // OK, temporary destination
                }
                else
                if (queueName.equals(FFMQConstants.ADM_REQUEST_QUEUE))
                {
                    conn.checkPermission(Resource.SERVER, Action.REMOTE_ADMIN);
                }
                else
                if (queueName.equals(FFMQConstants.ADM_REPLY_QUEUE))
                {
                    // Only the internal admin thread can produce on this queue
                    if (conn.getSecurityContext() != null)
                        throw new FFMQException("Access denied to administration queue "+queueName,"ACCESS_DENIED");
                }
                else
                {
                    // Standard queue
                    conn.checkPermission(destination,Action.PRODUCE);
                }
            }
            else
            if (destination instanceof Topic)
            {
                String topicName = ((Topic)destination).getTopicName();
                if (conn.isRegisteredTemporaryTopic(topicName))
                {
                    // OK, temporary destination
                }
                else
                {
                    // Standard topic
                    conn.checkPermission(destination,Action.PRODUCE);  
                }
            }
            else
                throw new InvalidDestinationException("Unsupported destination : "+destination);
        }
       
        if (debugEnabled)
            log.debug(this+" [PUT] in "+message.getJMSDestination()+" - "+message);
       
        synchronized (externalAccessLock)
        {
            checkNotClosed();
           
          if (transacted)
          {
            if (message.getJMSDestination() instanceof Queue)
              pendingQueuePuts.add(message);
            else
              pendingTopicPuts.add(message);
          }
          else
            putImmediately(message,null); // Send immediately
        }
    }
   
    /**
     * Put the given message in its destination immediately.
     * Updates the provided committables set if necessary.
   */
  private void putImmediately( AbstractMessage message , Set committables ) throws JMSException
    {
    Destination destination = message.getJMSDestination();
   
    if (destination instanceof Queue)
    {
      Queue queueRef = (Queue)destination;
      LocalQueue queue = engine.getLocalQueue(queueRef.getQueueName());
      if (queue.put(message))
        if (committables != null)
          committables.add(queue);
    }
    else
    if (destination instanceof Topic)
    {
      Topic topicRef = (Topic)destination;
      LocalTopic topic = engine.getLocalTopic(topicRef.getTopicName());
          topic.put(message,this,committables);
    }
    else
      throw new InvalidDestinationException("Unsupported destination : "+destination);
    }
 
  /**
   * Put a message in its target queue in 'locked' state.
   * Updates the provided committables set if necessary.
   */
  private void putLocked( AbstractMessage message , MessageLockSet locks , Set committables ) throws JMSException
    {
    Destination destination = message.getJMSDestination();
   
    if (destination instanceof Queue)
    {
      Queue queueRef = (Queue)destination;
      LocalQueue queue = engine.getLocalQueue(queueRef.getQueueName());
      if (queue.putLocked(message, locks))
        committables.add(queue);
    }
    else
      throw new InvalidDestinationException("Unsupported destination : "+destination);
    }
     
    /*
     * (non-Javadoc)
     * @see javax.jms.Session#commit()
     */
    public final void commit() throws JMSException
    {
      commit(true,null);
    }
   
    /**
     * Commit pending put/get operations in this session
     * @param commitGets
     * @param deliveredMessageIDs
     * @throws JMSException
     */
    public final void commit( boolean commitGets , List deliveredMessageIDs ) throws JMSException
    {
      if (!transacted)
            throw new IllegalStateException("Session is not transacted"); // [JMS SPEC]
     
      synchronized (externalAccessLock)
    {
        checkNotClosed();  
          commitUpdates(commitGets,deliveredMessageIDs);
    }
    }
   
    /* (non-Javadoc)
     * @see javax.jms.Session#rollback()
     */
    public final void rollback() throws JMSException
    {
      rollback(true, null);
    }

    /**
     * Rollback pending put/get operations in this session
     * @param rollbackGets
     * @param deliveredMessageIDs
     * @throws JMSException
     */
    public final void rollback( boolean rollbackGets, List deliveredMessageIDs ) throws JMSException
    {
      if (!transacted)
            throw new IllegalStateException("Session is not transacted"); // [JMS SPEC]
     
      synchronized (externalAccessLock)
    {
        checkNotClosed();
          rollbackUpdates(true,rollbackGets, deliveredMessageIDs);
    }
    }

    /**
     * Rollback undelivered get operations in this session
     * @param undeliveredMessageIDs
     * @throws JMSException
     */
    public final void rollbackUndelivered( List undeliveredMessageIDs ) throws JMSException
    {
      synchronized (externalAccessLock)
    {
        checkNotClosed();
          rollbackUpdates(false,true, undeliveredMessageIDs);
    }
    }
   
    private void commitUpdates( boolean commitGets , List deliveredMessageIDs ) throws JMSException
    {
      Set committables = new HashSet();
     
      // Send all pending messages
      if (transacted)
      {       
        // Queue messages
        synchronized (pendingQueuePuts)
        {
            if (!pendingQueuePuts.isEmpty())
            {
              if (debugEnabled)
                    log.debug(this+" - COMMIT [PUT:QUEUE] "+pendingQueuePuts.size()+" message(s)");
               
              // Simpler (and most common) use case
              if (pendingQueuePuts.size() == -1)
              {
                AbstractMessage message = (AbstractMessage)pendingQueuePuts.get(0);
                putImmediately(message,committables);               
                producedCount++;
              }
              else
              {
                // A two phase injection is required because one destination may throw
                // an exception while messages have already been added to queues (queue full for example)
               
                // Step 1 - Put messages in locked state
                MessageLockSet locks = new MessageLockSet(pendingQueuePuts.size());
                try
                {
                  for (int i = 0; i < pendingQueuePuts.size(); i++)
                  {
                    AbstractMessage message = (AbstractMessage)pendingQueuePuts.get(i);
                    putLocked(message, locks, committables);
                    producedCount++;
                }
                }
                catch (JMSException e)
                {
                  // Remove locked messages
                  for (int i = 0; i < locks.size(); i++)
              {
                MessageLock item = locks.get(i);
                item.getDestination().removeLocked(item);
              }
                 
                  throw e;
                }
               
                // Step 2 - Unlock & deliver messages
                for (int i = 0; i < locks.size(); i++)
            {
              MessageLock item = locks.get(i);
              item.getDestination().unlockAndDeliver(item);
            }
              }
             
              pendingQueuePuts.clear();
            }
        }
       
        // Topic messages
        synchronized (pendingTopicPuts)
        {
            if (!pendingTopicPuts.isEmpty())
            {
              if (debugEnabled)
                    log.debug(this+" - COMMIT [PUT:TOPIC] "+pendingTopicPuts.size()+" message(s)");
               
              for (int i = 0; i < pendingTopicPuts.size(); i++)
          {
                AbstractMessage message = (AbstractMessage)pendingTopicPuts.get(i);
                  putImmediately(message,committables);               
                  producedCount++; 
          }
             
              pendingTopicPuts.clear();
            }
        }
      }
     
      // Commit pending get messages
      if (commitGets && transactionSet.size() > 0)
      {
        TransactionItem[] items;
        if (deliveredMessageIDs != null)
        {
          // Commit only delivered messages
          if (debugEnabled)
              log.debug(this+" - COMMIT [GET] "+deliveredMessageIDs.size()+" message(s)");
          items = transactionSet.clear(deliveredMessageIDs);
        }
        else
        {
          // Commit the whole transaction set
        if (debugEnabled)
              log.debug(this+" - COMMIT [GET] "+transactionSet.size()+" message(s)");
        items = transactionSet.clear();
        }
        consumedCount += items.length;
       
        List updatedQueues = computeUpdatedQueues(items);
            for (int i = 0; i < updatedQueues.size(); i++)
            {
                LocalQueue localQueue = (LocalQueue)updatedQueues.get(i);
                if (localQueue.remove(this,items))
                  committables.add(localQueue);
            }
      }
     
      // Commit destinations
      commitChanges(committables);
    }
   
    private void commitChanges( Set committableDestinations ) throws JMSException
    {
      if (committableDestinations.size() > 0)
      {
        SynchronizationBarrier commitBarrier = new SynchronizationBarrier();
       
        Iterator commitables = committableDestinations.iterator();
        while (commitables.hasNext())
        {
          Committable commitable = (Committable)commitables.next();
          commitable.commitChanges(commitBarrier);
        }
       
        try
        {
          commitBarrier.waitFor();
        }
        catch (InterruptedException e)
        {
          throw new JMSException("Commit barrier was interrupted");
        }
      }
    }
   
    private void rollbackUpdates( boolean rollbackPuts , boolean rollbackGets, List deliveredMessageIDs ) throws JMSException
    {
      // Clear pending put messages
      if (rollbackPuts && transacted)
      {
        if (!pendingQueuePuts.isEmpty())
        {
          if (debugEnabled)
              log.debug(this+" - ROLLBACK [PUT] "+pendingQueuePuts.size()+" message(s)");
         
          pendingQueuePuts.clear();
        }
      }
     
      // Rollback pending get messages
      if (rollbackGets && transactionSet.size() > 0)
      {
        Set committableDestinations = new HashSet();
       
        TransactionItem[] items;
      if (deliveredMessageIDs != null)
      {
        // Rollback only delivered messages
        if (debugEnabled)
              log.debug(this+" - ROLLBACK [GET] "+deliveredMessageIDs.size()+" message(s)");
        items = transactionSet.clear(deliveredMessageIDs);
      }
      else
      {
        // Rollback the whole transaction set
        if (debugEnabled)
              log.debug(this+" - ROLLBACK [GET] "+transactionSet.size()+" message(s)");
        items = transactionSet.clear();
      }
     
      List updatedQueues = computeUpdatedQueues(items);
        for (int i = 0; i < updatedQueues.size(); i++)
      {
        LocalQueue localQueue = (LocalQueue)updatedQueues.get(i);
        if (localQueue.redeliver(this,items))
          committableDestinations.add(localQueue);
      }
       
        // Commit destinations
        commitChanges(committableDestinations);
      }
    }
   
    private List computeUpdatedQueues( TransactionItem[] items )
    {
        List updatedQueues = new ArrayList(Math.max(items.length,16));
        for (int i = 0 ; i < items.length ; i++)
        {
            LocalQueue localQueue = items[i].getDestination();
            if (!updatedQueues.contains(localQueue))
                updatedQueues.add(localQueue);
        }
        return updatedQueues;
    }
   
    private boolean hasPendingUpdates()
    {
      return transactionSet.size() > 0 || pendingQueuePuts.size() > 0;
    }
   
    /**
   * @return the transactionSet
   */
  protected final TransactionSet getTransactionSet()
  {
    return transactionSet;
  }
 
  /* (non-Javadoc)
   * @see net.timewalker.ffmq3.common.session.AbstractSession#onSessionClose()
   */
  protected void onSessionClose()
  {
      // Rollback updates
      try
      {
          if (hasPendingUpdates())
                rollbackUpdates(true,true, null);
      }
        catch (JMSException e)
        {
            ErrorTools.log(e, log);
        }
     
      super.onSessionClose();
    }
   
    /* (non-Javadoc)
     * @see javax.jms.Session#createBrowser(javax.jms.Queue, java.lang.String)
     */
    public QueueBrowser createBrowser(Queue queueRef, String messageSelector) throws JMSException
    {
      return createBrowser(idProvider.createID(), queueRef, messageSelector);
    }
   
    public QueueBrowser createBrowser(IntegerID browserId,Queue queueRef, String messageSelector) throws JMSException
    {
      synchronized (externalAccessLock)
    {
          checkNotClosed();
          LocalQueue localQueue = engine.getLocalQueue(queueRef.getQueueName());
 
          // Check temporary destinations scope (JMS Spec 4.4.3 p2)
          checkTemporaryDestinationScope(localQueue);
         
          LocalQueueBrowser browser = new LocalQueueBrowser(this,localQueue,messageSelector,browserId);
          registerBrowser(browser);
          return browser;
    }
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createConsumer(javax.jms.Destination, java.lang.String, boolean)
     */
    public MessageConsumer createConsumer(Destination destination, String messageSelector, boolean noLocal) throws JMSException
    {
      return createConsumer(idProvider.createID(), destination, messageSelector, noLocal);
    }

    /**
     * Create a consumer with the given id
     */
    public MessageConsumer createConsumer(IntegerID consumerId,Destination destination, String messageSelector, boolean noLocal) throws JMSException
    {
      synchronized (externalAccessLock)
    {
          checkNotClosed();
          LocalMessageConsumer consumer = new LocalMessageConsumer(engine,this,destination,messageSelector,noLocal,consumerId,null);
          registerConsumer(consumer);
          consumer.initDestination();
          return consumer;
    }
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createDurableSubscriber(javax.jms.Topic, java.lang.String, java.lang.String, boolean)
     */
    public TopicSubscriber createDurableSubscriber(Topic topic, String subscriptionName, String messageSelector, boolean noLocal) throws JMSException
    {
      return createDurableSubscriber(idProvider.createID(), topic, subscriptionName, messageSelector, noLocal);
    }
   
    public TopicSubscriber createDurableSubscriber(IntegerID consumerId, Topic topic, String subscriptionName, String messageSelector, boolean noLocal) throws JMSException
    {
      if (StringTools.isEmpty(subscriptionName))
            throw new FFMQException("Empty subscription name","INVALID_SUBSCRIPTION_NAME");
     
      synchronized (externalAccessLock)
    {
          checkNotClosed();
         
          // Get the client ID
          String clientID = connection.getClientID();
         
          // Create the consumer
          String subscriberId = clientID+"-"+subscriptionName;
          LocalDurableTopicSubscriber subscriber = new LocalDurableTopicSubscriber(engine,this,topic,messageSelector,noLocal,consumerId,subscriberId);
          registerConsumer(subscriber);
          subscriber.initDestination();
         
          // Register the subscription
          engine.subscribe(clientID, subscriptionName);
         
          return subscriber;
    }
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#createProducer(javax.jms.Destination)
     */
    public MessageProducer createProducer(Destination destination) throws JMSException
    {
      synchronized (externalAccessLock)
    {
          checkNotClosed();
          LocalMessageProducer producer = new LocalMessageProducer(this,destination,idProvider.createID());
          registerProducer(producer);
          return producer;
    }
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#recover()
     */
    public final void recover() throws JMSException
    {
      recover(null);
    }
   
    /**
     * @see #rollback(boolean, List)
     */
    public final void recover( List deliveredMessageIDs ) throws JMSException
    {
      synchronized (externalAccessLock)
    {
        checkNotClosed();
          if (transacted)
              throw new IllegalStateException("Session is transacted"); // [JMS SPEC]
 
          rollbackUpdates(true,true, deliveredMessageIDs);
    }
    }

    /* (non-Javadoc)
     * @see javax.jms.Session#unsubscribe(java.lang.String)
     */
    public void unsubscribe(String subscriptionName) throws JMSException
    {
      synchronized (externalAccessLock)
    {
          checkNotClosed();
          if (StringTools.isEmpty(subscriptionName))
              throw new FFMQException("Empty subscription name","INVALID_SUBSCRIPTION_NAME");
 
          // Remove remaining subscriptions on all topics
          engine.unsubscribe(connection.getClientID(), subscriptionName);
    }
    }
   
    /* (non-Javadoc)
     * @see javax.jms.Session#createTemporaryQueue()
     */
    public TemporaryQueue createTemporaryQueue() throws JMSException
    {  
      synchronized (externalAccessLock)
    {
          checkNotClosed();
          String queueName = "TEMP-QUEUE-"+UUIDProvider.getInstance().getShortUUID();
          engine.createTemporaryQueue(queueName);
          connection.registerTemporaryQueue(queueName);
         
          return new TemporaryQueueRef(connection,queueName);
    }
    }
   
    /* (non-Javadoc)
     * @see javax.jms.Session#createTemporaryTopic()
     */
    public TemporaryTopic createTemporaryTopic() throws JMSException
    {
      synchronized (externalAccessLock)
    {
          checkNotClosed();
          String topicName = "TEMP-TOPIC-"+UUIDProvider.getInstance().getShortUUID();
          engine.createTemporaryTopic(topicName);
          connection.registerTemporaryTopic(topicName);
         
          return new TemporaryTopicRef(connection,topicName);
    }
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.common.session.AbstractSession#acknowledge()
     */
    public final void acknowledge() throws JMSException
    {     
      acknowledge(null);
    }
   
    /**
     * @see #commit(boolean,List)
     */
    public final void acknowledge( List deliveredMessageIDs ) throws JMSException
    {     
        if (transacted)
            throw new IllegalStateException("Session is transacted"); // [JMS SPEC]
       
      synchronized (externalAccessLock)
    {
          checkNotClosed();
          commitUpdates(true,deliveredMessageIDs);
    }
    }
   
    /**
     * Delete a queue
     * @param queueName
     * @throws JMSException
     */
    protected final void deleteQueue( String queueName ) throws JMSException
    {
      transactionSet.removeUpdatesForQueue(queueName);
        engine.deleteQueue(queueName);
    }
   
    /**
     * Get the number of messages actually produced by this session
   * @return the number of messages actually produced by this session
   */
  public final long getProducedCount()
  {
    return producedCount;
  }
   
  /**
     * Get the number of messages actually consumed by this session
   * @return the number of messages actually consumed by this session
   */
  public final long getConsumedCount()
  {
    return consumedCount;
  }
 
  /*
     *  (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
        StringBuffer sb = new StringBuffer();
        sb.append(super.toString());
        sb.append("(consumed=");
        sb.append(consumedCount);
        sb.append(",produced=");
        sb.append(producedCount);
        sb.append(")");
       
        return sb.toString();
    }
}
TOP

Related Classes of net.timewalker.ffmq3.local.session.LocalSession

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.