Package net.timewalker.ffmq3.local.destination

Source Code of net.timewalker.ffmq3.local.destination.LocalQueue

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

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Queue;

import net.timewalker.ffmq3.FFMQException;
import net.timewalker.ffmq3.common.message.AbstractMessage;
import net.timewalker.ffmq3.common.message.MessageSelector;
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.session.LocalMessageConsumer;
import net.timewalker.ffmq3.local.session.LocalQueueBrowserCursor;
import net.timewalker.ffmq3.local.session.LocalSession;
import net.timewalker.ffmq3.management.destination.definition.QueueDefinition;
import net.timewalker.ffmq3.storage.data.DataStoreFullException;
import net.timewalker.ffmq3.storage.message.MessageSerializationLevel;
import net.timewalker.ffmq3.storage.message.MessageStore;
import net.timewalker.ffmq3.storage.message.impl.BlockFileMessageStore;
import net.timewalker.ffmq3.storage.message.impl.InMemoryMessageStore;
import net.timewalker.ffmq3.utils.Committable;
import net.timewalker.ffmq3.utils.ErrorTools;
import net.timewalker.ffmq3.utils.concurrent.CopyOnWriteList;
import net.timewalker.ffmq3.utils.concurrent.SynchronizationBarrier;
import net.timewalker.ffmq3.utils.watchdog.ActiveObject;
import net.timewalker.ffmq3.utils.watchdog.ActivityWatchdog;

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

/**
* <p>Implementation for a local JMS {@link Queue}</p>
*/
public final class LocalQueue extends AbstractLocalDestination implements Queue, LocalQueueMBean, ActiveObject, Committable
{  
    private static final Log log = LogFactory.getLog(LocalQueue.class);

    // Scheduler thread handling delayed redeliveries
    private static final Timer redeliveryTimer = new Timer(true);
   
    // Definition
    private FFMQEngine engine;
    private QueueDefinition queueDef;
   
    // Message store
    private MessageStore volatileStore;
    private MessageStore persistentStore;
    private Object storeLock = new Object();
  
    // Statistics
    private volatile long sentToQueueCount = 0;
    private volatile long receivedFromQueueCount = 0;
    private volatile long acknowledgedGetCount = 0;
    private volatile long rollbackedGetCount = 0;
   
    // Settings
    private long inactivityTimeout;
    private long redeliveryDelay;
    private boolean traceEnabled = log.isTraceEnabled();
   
    // Runtime
    private long lastActivity;
    private volatile int consumerOffset = 0; // Used for a round-robin-like consumer wake-up
   
    /**
     * Constructor
     */
    public LocalQueue( FFMQEngine engine , QueueDefinition queueDef ) throws JMSException
    {
        super(queueDef);
        this.engine = engine;
        this.queueDef = queueDef;
       
        // Init volatile store
        if (queueDef.getMaxNonPersistentMessages() > 0)
        {
            this.volatileStore = new InMemoryMessageStore(queueDef);
            this.volatileStore.init();
        }
       
        // Init persistent store
        if (queueDef.hasDataFiles())
        {
            this.persistentStore = new BlockFileMessageStore(queueDef,engine.getDiskIOAsyncTaskManager());
            this.persistentStore.init();
        }
       
        this.inactivityTimeout = engine.getSetup().getWatchdogConsumerInactivityTimeout()*1000L;
        this.redeliveryDelay = engine.getSetup().getRedeliveryDelay();
        this.lastActivity = System.currentTimeMillis();
        ActivityWatchdog.getInstance().register(this);
    }
   
    /**
     * Get the queue definition
     */
    public QueueDefinition getDefinition()
    {
        return queueDef;
    }
   
    /*
     * (non-Javadoc)
     * @see javax.jms.Queue#getQueueName()
     */
    public String getQueueName()
    {
        return getName();
    }

    /**
     * Put a new message in the queue.
     * Listeners are automatically notified of the new message availability.
     * @return true if a commit is required to ensure data safety
     */
    public boolean put( AbstractMessage message ) throws JMSException
    {
      return put(message,null);
    }
   
    /**
     * Put a new message in the queue. The message is locked and the lock registered in the provided handle set
     * @return true if a commit is required to ensure data safety
     */
    public boolean putLocked( AbstractMessage message , MessageLockSet locks ) throws JMSException
    {
      return put(message,locks);
    }

    private boolean put( AbstractMessage message , MessageLockSet locks ) throws JMSException
    {
      checkNotClosed();
     
      // Consistency check
      if (!message.isInternalCopy())
        throw new FFMQException("Message instance is not an FFMQ internal copy !","CONSISTENCY_ERROR");
     
      // Dispatch message to the adequate store
      MessageStore targetStore;
      if (message.getJMSDeliveryMode() == DeliveryMode.NON_PERSISTENT)
      {
          // Use volatile store if possible, otherwise fallback to persistent store
          targetStore = volatileStore != null ? volatileStore : persistentStore;
      }
      else
          targetStore = persistentStore;
         
      if (targetStore == null)
          throw new FFMQException("Queue does not support this delivery mode : "+
                                  (message.getJMSDeliveryMode() == DeliveryMode.NON_PERSISTENT ?
                                  "DeliveryMode.NON_PERSISTENT" : "DeliveryMode.PERSISTENT"),
                                  "INVALID_DELIVERY_MODE");
     
      int newHandle;
        synchronized (storeLock)
        {
            newHandle = targetStore.store(message);
            if (newHandle == -1)
            {
              // No space left for this message in the target store
              if (targetStore == volatileStore && persistentStore != null)
              {
                // Fallback to persistent store if possible
                targetStore = persistentStore;
                newHandle = targetStore.store(message);
              }
              // Cannot store the message anywhere
              if (newHandle == -1)
                throw new DataStoreFullException("Cannot store message : queue is full : "+getName());
            }
           
            if (locks != null)
          {
              targetStore.lock(newHandle);
              locks.add(newHandle, targetStore.getDeliveryMode(), this);
          }
        }
        sentToQueueCount++;
       
        // Wake up waiting consumers
        if (locks == null)
            messageAvailable(message);
       
        return (message.getJMSDeliveryMode() == DeliveryMode.PERSISTENT);
    }
   
    /**
     * Unlock a message.
     * Listeners are automatically notified of the new message availability.
     */
    public void unlockAndDeliver( MessageLock lockRef ) throws JMSException
    {
      MessageStore targetStore;
      if (lockRef.getDeliveryMode() == DeliveryMode.NON_PERSISTENT)
          targetStore = volatileStore;
      else
          targetStore = persistentStore;
     
      AbstractMessage message;
      synchronized (storeLock)
        {
        int handle = lockRef.getHandle();
        message = targetStore.retrieve(handle);
        targetStore.unlock(handle);
        }
     
      messageAvailable(message);
    }
   
    /**
     * Remove a locked message from the queue
     */
    public void removeLocked( MessageLock lockRef ) throws JMSException
    {
      MessageStore targetStore;
      if (lockRef.getDeliveryMode() == DeliveryMode.NON_PERSISTENT)
          targetStore = volatileStore;
      else
          targetStore = persistentStore;
     
      synchronized (storeLock)
        {
        targetStore.delete(lockRef.getHandle());
        }
    }
   
    /**
     * Commit get operations on this queue (messages are removed)
     * @return true if a commit is required to ensure data safety
     */
    public boolean remove( LocalSession localSession , TransactionItem[] items ) throws JMSException
    {
      int volatileCommitted = 0;
      int persistentCommitted = 0;
     
        synchronized (storeLock)
        {
        for (int n = 0 ; n < items.length ; n++)
            {
          TransactionItem transactionItem = items[n];
              if (transactionItem.getDestination() != this)
                continue;
             
              if (traceEnabled)
                  log.trace(localSession+" COMMIT "+transactionItem.getMessageId());
             
              // Delete message from store
              if (transactionItem.getDeliveryMode() == DeliveryMode.PERSISTENT)
              {
                persistentStore.delete(transactionItem.getHandle());
                persistentCommitted++;
              }
              else
              {
                volatileStore.delete(transactionItem.getHandle());
                volatileCommitted++;
              }
            }
           
          acknowledgedGetCount += volatileCommitted + persistentCommitted;
        }
       
        return persistentCommitted > 0;
    }
   
    /**
     * Rollback get operations on this queue (messages are unlocked)
     * Consumers are notified of rollbacked messages availability
     * @return true if a commit is required to ensure data safety
     */
    public boolean redeliver( LocalSession localSession , TransactionItem[] items ) throws JMSException
    {
      int volatileRollbacked = 0;
      int persistentRollbacked = 0;
     
      List newEntries = null;
        synchronized (storeLock)
        {
            for (int n = 0 ; n < items.length ; n++)
            {
              TransactionItem transactionItem = items[n];
              if (transactionItem.getDestination() != this)
                continue; // Not for us
             
              MessageStore store = transactionItem.getDeliveryMode() == DeliveryMode.PERSISTENT ? persistentStore : volatileStore;
              int handle = transactionItem.getHandle();
             
              // Retrieve message content
               AbstractMessage msg = store.retrieve(handle);
              
               // Update redelivered flag both in memory and message store
               msg.setJMSRedelivered(true);
               handle = store.replace(handle, msg);
              
              if (redeliveryDelay > 0)
              {
                  // Keep the message locked so it cannot be re-consumed immediately                 
                // and schedule the message unlock afetr redeliveryDelay milliseconds
                redeliveryTimer.schedule(new RedeliveryTask(msg,store,handle),
                                     redeliveryDelay);
              }
              else
              {
                // Unlock message in store
                   store.unlock(handle);
                   if (traceEnabled)
                      log.trace(localSession+" UNLOCKED "+transactionItem.getMessageId());
                  
                   // Add to dispatch list               
                    if (newEntries == null)
                      newEntries = new ArrayList();
                    newEntries.add(msg);
              }

                if (transactionItem.getDeliveryMode() == DeliveryMode.PERSISTENT)
                  persistentRollbacked++;
                else
                  volatileRollbacked++;
            }
           
            rollbackedGetCount += volatileRollbacked + persistentRollbacked;
        }

        // Dispatch notifications
        if (newEntries != null)
            for (int n = 0 ; n < newEntries.size() ; n++)
                messageAvailable((AbstractMessage)newEntries.get(n));
       
        return persistentRollbacked > 0;
    }
   
    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.utils.Commitable#commitChanges(net.timewalker.ffmq3.utils.concurrent.SynchronizationBarrier)
     */
    public void commitChanges( SynchronizationBarrier barrier ) throws JMSException
    {
      if (persistentStore != null)
      {
        synchronized (storeLock)
      {
          persistentStore.commitChanges(barrier);
      }
      }
    }
   
    /** See RedeliveryTask */
    protected void redeliverMessage( AbstractMessage msg , MessageStore store , int handle )
    {
      try
      {
        synchronized (storeLock)
          {
          // Unlock message in store
           store.unlock(handle);
           if (traceEnabled)
              log.trace("(Deferred) UNLOCKED "+msg.getJMSMessageID());
          }
       
        // Dispatch notification
        messageAvailable(msg);
      }
      catch (JMSException e)
      {
        ErrorTools.log(e, log);
      }
    }

    /**
     * Get a message from this destination
     */
    public AbstractMessage get( LocalSession localSession ,
                  TransactionSet transactionSet ,
                                MessageSelector selector ) throws JMSException
    {
      if (closed)
        return null;
     
      this.lastActivity = System.currentTimeMillis();
     
      AbstractMessage msg = null;
     
        // Search in volatile store first
        if (volatileStore != null)
        {
            msg = getFromStore(localSession, volatileStore, transactionSet, selector);
           
            // Then in persistent store
          if (msg == null && persistentStore != null)
            msg = getFromStore(localSession, persistentStore, transactionSet, selector);
        }
        else
        if (persistentStore != null)
          msg = getFromStore(localSession, persistentStore, transactionSet, selector);
       
        return msg;
    }
   
    /**
     * Browse a message in this queue
     * @param cursor browser cursor
     * @param selector a message selector or null
     * @return a matching message or null
     * @throws JMSException on internal error
     */
    public AbstractMessage browse( LocalQueueBrowserCursor cursor , MessageSelector selector ) throws JMSException
    {
      // Reset cursor to last known position
      cursor.reset();
     
      // Search in volatile store first
        if (volatileStore != null)
        {
            AbstractMessage msg = browseStore(volatileStore, cursor, selector);
            if (msg != null)
            {
              cursor.move();
                return msg;
            }
        }
       
        // Then in persistent store
        if (persistentStore != null)
        {
          AbstractMessage msg = browseStore(persistentStore, cursor, selector);
          if (msg != null)
            {
              cursor.move();
                return msg;
            }
        }
       
        // Nothing found, set EOQ flag on cursor
        cursor.setEndOfQueueReached();
       
        return null;
    }
   
    private AbstractMessage browseStore( MessageStore store , LocalQueueBrowserCursor cursor , MessageSelector selector ) throws JMSException
    {
      long now = System.currentTimeMillis();
      synchronized (storeLock)
        {
            int current = store.first();
           
            // Skip to initial position
            while (current != -1 && cursor.position() > cursor.skipped())
            {
              cursor.skip();
              current = store.next(current);
            }
           
            while (current != -1)
            {
                // Skip locked messages
                if (!store.isLocked(current))
                {
                    // Retrieve the message
                    AbstractMessage msg = store.retrieve(current);
                   
                    // Check expiration
                    if (msg.getJMSExpiration() > 0 && msg.getJMSExpiration() < now)
                    {
                      int handleToDelete = current;
                      current = store.next(current);
                      store.delete(handleToDelete);
                      continue;
                    }
                   
                    // Check selector
                    if (selector == null)
                    {
                        return msg;
                    }
                    else
                    {
                      msg.ensureDeserializationLevel(MessageSerializationLevel.ALL_HEADERS);
                      if (selector.matches(msg))
                        return msg;
                    }
                }
               
                cursor.skip();
                current = store.next(current);
            }
           
            return null;
        }
    }
   
    private AbstractMessage getFromStore( LocalSession localSession ,
                                          MessageStore store ,
                                          TransactionSet transactionSet ,
                                          MessageSelector selector ) throws JMSException
    {
        synchronized (storeLock)
        {
            int current = store.first();
            while (current != -1)
            {
                // Skip locked messages
                if (!store.isLocked(current))
                {
                    // Retrieve the message
                    AbstractMessage msg = store.retrieve(current);
  
                    // Check expiration
                    if (msg.getJMSExpiration() > 0 && msg.getJMSExpiration() < lastActivity)
                    {
                      int handleToDelete = current;
                      current = store.next(current);
                      store.delete(handleToDelete);
                      continue;
                    }
                   
                    // Check selector
                    boolean matchesSelector;
                    if (selector != null)
                    {
                      msg.ensureDeserializationLevel(MessageSerializationLevel.ALL_HEADERS);
                      matchesSelector = selector.matches(msg);
                    }
                    else
                      matchesSelector = true;
                   
                    if (matchesSelector)
                    {
                        store.lock(current);
                       
                        if (traceEnabled)
                          log.trace(localSession+" LOCKED "+msg.getJMSMessageID());
                       
                        transactionSet.add(current,
                                       msg.getJMSMessageID(),
                                       store.getDeliveryMode(),
                                       this);
                        receivedFromQueueCount++;
                        return msg;
                    }
                }
               
                current = store.next(current);
            }
           
            return null;
        }
    }
   
    /**
     * Purge some messages from the buffer
     */
    public void purge( MessageSelector selector ) throws JMSException
    {
        if (volatileStore != null)
            purgeStore(volatileStore,selector);
        if (persistentStore != null)
            purgeStore(persistentStore,selector);
    }
   
    private void purgeStore( MessageStore store , MessageSelector selector ) throws JMSException
    {
        synchronized (storeLock)
        {
            int current = store.first();
            while (current != -1)
            {
                int next = store.next(current);
               
                // Skip locked messages
                if (!store.isLocked(current))
                {
                  if (selector != null)
                  {
                    AbstractMessage msg = store.retrieve(current);
                    msg.ensureDeserializationLevel(MessageSerializationLevel.ALL_HEADERS);
                        if (selector.matches(msg))
                          store.delete(current);
                  }
                  else
                    store.delete(current);
                }
               
                current = next;
            }
        }
    }
   
    /**
     * Called when a message is available
     */
    protected void messageAvailable( AbstractMessage message )
    {
      LocalMessageConsumer singleConsumer = null;
      CopyOnWriteList consumersSnapshot = null;
      synchronized (localConsumers)
    {
        switch (localConsumers.size())
        {
          case 0 : return; // Nobody's listening
          case 1 : singleConsumer = (LocalMessageConsumer)localConsumers.get(0); break; // Single consumer
          default : // Multiple consumers
            consumersSnapshot = localConsumers.fastCopy();   
        }
    }

      if (singleConsumer != null)
        notifySingleConsumer(singleConsumer,message);
      else
        notifyNextConsumer(consumersSnapshot,message);
    }
   
    private void notifySingleConsumer( LocalMessageConsumer consumer , AbstractMessage message )
    {
      try
        {       
          // Check message selector
          if (message != null)
          {   
                MessageSelector consumerSelector = consumer.getReceiveSelector();
                if (consumerSelector != null)
                {
                  message.ensureDeserializationLevel(MessageSerializationLevel.ALL_HEADERS);
                  if (!consumerSelector.matches(message))
                    return;
                }
          }
 
        if (consumer.mayBlock())
          engine.getDeliveryAsyncTaskManager().execute(consumer);
        else
          consumer.messageAvailable();
    }
        catch (JMSException e)
      {
        ErrorTools.log(e, log);
        consumer.getSession().getConnection().exceptionOccured(e);
      }
    }
   
    private void notifyNextConsumer( CopyOnWriteList consumersSnapshot , AbstractMessage message )
    {
      // Find a consumer to notify
      int localConsumersCount = consumersSnapshot.size();
      int currentOffset = consumerOffset++; // Copy current offset (value is volatile and should not change during the following loop)
      for (int n = 0 ; n < localConsumersCount ; n++)
      {
            int offset = ((n+currentOffset) % localConsumersCount);
            LocalMessageConsumer consumer = (LocalMessageConsumer)consumersSnapshot.get(offset);
           
            // Check that the consumer connection is started
            if (!consumer.getSession().getConnection().isStarted())
              continue;
           
            // Check message selector
            if (message != null)
            {
              MessageSelector consumerSelector = consumer.getReceiveSelector();
              if (consumerSelector != null)
                {
                message.ensureDeserializationLevel(MessageSerializationLevel.ALL_HEADERS);
               
                  try
                  {
                      if (!consumerSelector.matches(message))
                        continue;
                  }
                  catch (JMSException e)
                  {
                    ErrorTools.log(e, log);
                      consumer.getSession().getConnection().exceptionOccured(e);
                      break;
                  }
                }
            }

            try
        {
              if (consumer.mayBlock())
                engine.getDeliveryAsyncTaskManager().execute(consumer);
              else
                consumer.messageAvailable();
              break;
        }
            catch (JMSException e)
          {
            ErrorTools.log(e, log);
            consumer.getSession().getConnection().exceptionOccured(e);
          }
      }
    }
   
    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.local.destination.LocalDestinationMBean#getSize()
     */
    public int getSize()
    {
        int size = 0;
        synchronized (storeLock)
        {
            if (volatileStore != null)
                size += volatileStore.size();
            if (persistentStore != null)
                size += persistentStore.size();
        }
        return size;
    }
   
    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.local.destination.LocalDestinationMBean#resetStats()
     */
    public void resetStats()
    {
      sentToQueueCount = 0;
      receivedFromQueueCount = 0;
      acknowledgedGetCount = 0;
      rollbackedGetCount = 0;
    }
   
    /*
     *  (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString()
    {
       StringBuffer sb = new StringBuffer();
      
       sb.append("Queue{");
       sb.append(getName());
       sb.append("}[size=");
       sb.append(getSize());
       sb.append(",consumers=");
       sb.append(localConsumers.size());
       sb.append(",in=");
       sb.append(sentToQueueCount);
       sb.append(",out=");
       sb.append(receivedFromQueueCount);
       sb.append(",ack=");
       sb.append(acknowledgedGetCount);
       sb.append(",rollback=");
       sb.append(rollbackedGetCount);
       sb.append("]");
      
       return sb.toString();
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.local.destination.AbstractLocalDestination#close()
     */
    public final void close() throws JMSException
    {
      synchronized (closeLock)
    {
        if (closed)
          return;
        closed = true;
    }
     
      ActivityWatchdog.getInstance().unregister(this);
     
      synchronized (storeLock)
    {
          if (volatileStore != null)
          {
              volatileStore.close();
             
              // Delete message store if the queue was temporary
              if (queueDef.isTemporary())
                  volatileStore.delete();
          }
          if (persistentStore != null)
          {
              persistentStore.close();
             
              // Delete message store if the queue was temporary
              if (queueDef.isTemporary())
                  persistentStore.delete();
          }
    }
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.local.destination.LocalQueueMBean#getSentToQueueCount()
     */
    public long getSentToQueueCount()
    {
        return sentToQueueCount;
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.local.destination.LocalQueueMBean#getReceivedFromQueueCount()
     */
    public long getReceivedFromQueueCount()
    {
        return receivedFromQueueCount;
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.local.destination.LocalQueueMBean#getAcknowledgedGetCount()
     */
    public long getAcknowledgedGetCount()
    {
        return acknowledgedGetCount;
    }

    /*
     * (non-Javadoc)
     * @see net.timewalker.ffmq3.local.destination.LocalQueueMBean#getRollbackedGetCount()
     */
    public long getRollbackedGetCount()
    {
        return rollbackedGetCount;
    }
   
    /* (non-Javadoc)
     * @see net.timewalker.ffmq3.utils.watchdog.ActiveObject#getLastActivity()
     */
    public long getLastActivity()
    {
      return lastActivity;
    }
   
    /* (non-Javadoc)
     * @see net.timewalker.ffmq3.utils.watchdog.ActiveObject#getTimeoutDelay()
     */
    public long getTimeoutDelay()
    {
      return inactivityTimeout;
    }
   
    /* (non-Javadoc)
     * @see net.timewalker.ffmq3.utils.watchdog.ActiveObject#onActivityTimeout()
     */
    public boolean onActivityTimeout() throws Exception
    {
      if (closed)
        return true;
     
      if (getSize() == 0)
        return false; // Queue is empty
     
      // Notify next consumer
      messageAvailable(null);

      return false;
    }
   
    //-------------------------------------------------------------------------------
   
    private class RedeliveryTask extends TimerTask
    {
      // Attributes
      private AbstractMessage msg;
      private MessageStore store;
      private int handle;
     
      /**
     * Constructor
     */
    public RedeliveryTask( AbstractMessage msg , MessageStore store , int handle )
    {
      this.msg = msg;
      this.store = store;
      this.handle = handle;
    }
     
      /* (non-Javadoc)
       * @see java.util.TimerTask#run()
       */
      public void run()
      {
        redeliverMessage(msg,store,handle);
      }
    }
}
TOP

Related Classes of net.timewalker.ffmq3.local.destination.LocalQueue

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.