Package nexj.core.rpc.queueing

Source Code of nexj.core.rpc.queueing.ObjectQueueDispatcher$Delivery

package nexj.core.rpc.queueing;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.naming.InitialContext;
import javax.resource.spi.ManagedConnectionFactory;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;

import nexj.core.integration.ContextReceiver;
import nexj.core.integration.IntegrationException;
import nexj.core.meta.Attribute;
import nexj.core.meta.Component;
import nexj.core.meta.Metaclass;
import nexj.core.meta.Metadata;
import nexj.core.meta.Primitive;
import nexj.core.meta.Repository;
import nexj.core.meta.integration.Channel;
import nexj.core.meta.integration.channel.queueing.ObjectDispatcherQueue;
import nexj.core.meta.integration.channel.queueing.ObjectQueue;
import nexj.core.persistence.OID;
import nexj.core.persistence.Query;
import nexj.core.rpc.InstanceFactory;
import nexj.core.rpc.RPCException;
import nexj.core.rpc.RPCUtil;
import nexj.core.rpc.ServerException;
import nexj.core.rpc.TransferObject;
import nexj.core.runtime.ActionContext;
import nexj.core.runtime.Instance;
import nexj.core.runtime.InstanceList;
import nexj.core.runtime.InvocationContext;
import nexj.core.runtime.ThreadContextHolder;
import nexj.core.scripting.Function;
import nexj.core.scripting.Machine;
import nexj.core.scripting.Pair;
import nexj.core.scripting.Symbol;
import nexj.core.util.Binary;
import nexj.core.util.BinaryHeap;
import nexj.core.util.ComparableComparator;
import nexj.core.util.HashHolder;
import nexj.core.util.HashTab;
import nexj.core.util.Heap;
import nexj.core.util.J2EEUtil;
import nexj.core.util.Lifecycle;
import nexj.core.util.Logger;
import nexj.core.util.Lookup;
import nexj.core.util.ObjUtil;
import nexj.core.util.SysUtil;
import nexj.core.util.UncheckedException;
import nexj.core.util.auth.SimplePrincipal;

/**
* Concurrency control component to support the ObjectQueueDispatcher class.
*/
public class ObjectQueueDispatcher extends ContextReceiver implements ObjectDispatchListener, Lifecycle
{
   // constants

   /**
    * The attributes to read off a queue for message receiving.
    */
   public final static Pair RECEIVE_ATTRIBUTES = Pair.list(Symbol.define("body"), Pair.list(Symbol.define("queue"), Symbol
      .define("timeout")));

   /**
    * The ONE symbol, indicating that a message is to be received by one
    * arbitrary node.
    */
   public final static Symbol ONE_RECEIVER = Symbol.define("one");

   /**
    * The ALL symbol, indicating that a message is to be received by all nodes.
    */
   public final static Symbol ALL_RECEIVERS = Symbol.define("all");

   /**
    * The DISPATCHER symbol, indicating that a message is to be received by the
    * dispatcher in sequence with dispatcher operations.
    */
   public final static Symbol DISPATCHER_RECEIVER = Symbol.define("dispatcher");

   /**
    * The OTHER symbol, indicating that a message is to be received by all nodes
    * except the sender.
    */
   public final static Symbol OTHER_RECEIVERS = Symbol.define("other");

   // attributes

   /**
    * The next dispatch time returned by the most recent call to dispatch.
    */
   protected Timestamp m_tsNextDispatchTime;

   /**
    * The name of the dispatcher's queue.
    */
   protected String m_sQueueName;

   /**
    * True if running inside a J2EE container.
    */
   protected static boolean s_bContained = J2EEUtil.isContained();

   /**
    * True if the resource adapter has shutdown.
    */
   protected boolean m_bShutdown;

   /**
    * True if a dispatch request has been made and has not yet returned.
    */
   protected boolean m_bDispatch;

   /**
    * True if the component is enabled.
    */
   protected boolean m_bEnabled;

   /**
    * The most recent timestamp for which a message ordinal has been returned.
    */
   protected static Timestamp s_tsLastTimestamp;

   /**
    * The most recently returned message ordinal.
    */
   protected static int s_nOrdinal;

   // associations

   /**
    * Set of semaphores currently held on this node.
    */
   protected Lookup m_semaphoreMap = new HashTab();

   /**
    * Set of resources that have saturated semaphores.
    */
   protected Set m_blockingSet = new HashHolder();

   /**
    * Map of nodes by processing message Id.
    */
   protected Lookup m_nodeByProcessingMessageIdMap = new HashTab();

   /**
    * Map of sets of processing messages by Node.
    */
   protected Lookup m_processingMessagesByNodeNameMap = new HashTab();

   /**
    * Heap of messages deliverable to any node.
    */
   protected Heap m_deliverableAnyHeap = new BinaryHeap(ComparableComparator.INSTANCE);

   /**
    * Map of heaps of deliverable messages by node name.
    */
   protected Lookup m_deliverableHeapMap = new HashTab();

   /**
    * The attributes to load when reading a message.
    */
   protected Pair m_messageLoadAttributes;

   /**
    * The connection factory.
    */
   protected ObjectQueueConnectionFactory m_factory;

   /**
    * The metadata.
    */
   protected Metadata m_metadata;

   /**
    * The dispatcher channel stub.
    */
   protected ObjectDispatcherQueue m_channel;

   /**
    * The class logger.
    */
   protected final static Logger s_logger = Logger.getLogger(ObjectQueueDispatcher.class);

   // operations

   /**
    * @see nexj.core.runtime.Initializable#initialize()
    */
   public void initialize() throws Exception
   {
      super.initialize();

      if (!m_bEnabled)
      {
         return;
      }

      if (s_bContained)
      {
         String sFactory = J2EEUtil.JNDI_ENV_PREFIX + "queueing/ObjectQueue";

         if (s_logger.isInfoEnabled())
         {
            s_logger.info("Binding to connection factory \"" + sFactory + "\"");
         }

         m_factory = (ObjectQueueConnectionFactory)new InitialContext().lookup(sFactory);
      }
      else
      {
         s_logger.info("Binding to ObjectQueue resource adapter connection factory");

         ManagedConnectionFactory mcf = (ManagedConnectionFactory)Class.forName(
            SysUtil.PACKAGE + ".core.rpc.queueing.ra.ObjectQueueManagedConnectionFactory").newInstance();

         m_factory = (ObjectQueueConnectionFactory)mcf.createConnectionFactory();
      }
   }

   /**
    * @return the dispatcher channel.
    */
   public Channel getChannel()
   {
      return m_channel;
   }

   /**
    * The clientWake event.
    */
   public void clientWake()
   {
      if (m_factory == null)
      {
         return;
      }

      s_logger.debug("Sending dispatcher \"wake\" request");

      ObjectQueueConnection con = null;

      try
      {
         con = m_factory.open();
         con.wake();
      }
      catch (IntegrationException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new RPCException("err.rpc.msg", e);
      }
      finally
      {
         if (con != null)
         {
            con.close();
         }
      }
   }

   /**
    * @return the name of the current node.
    */
   protected String getNodeName()
   {
      return J2EEUtil.ISOLATED_NODE_NAME;
   }

   /**
    * The clientPost event. Posts a command for delivery to one or several
    * nodes.
    *
    * @param tobj the serializable message to post.
    */
   public void clientPost(TransferObject tobj)
   {
      if (m_factory == null)
      {
         return;
      }

      if (s_logger.isDebugEnabled())
      {
         s_logger.debug("Sending dispatcher \"post\" request: " + tobj);
      }

      ObjectQueueConnection con = null;

      try
      {
         con = m_factory.open();
         con.post(tobj);
      }
      catch (IntegrationException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new RPCException("err.rpc.msg", e);
      }
      finally
      {
         if (con != null)
         {
            con.close();
         }
      }
   }

   /**
    * Returns the heap associated with sNode, creating one if necessary.
    *
    * @param sNodeName the node for which to return a heap.
    * @return Heap the heap.
    */
   protected Heap getHeap(String sNodeName)
   {
      Heap heap = (Heap)m_deliverableHeapMap.get(sNodeName);

      if (heap == null)
      {
         if (isAvailable(sNodeName))
         {
            heap = new BinaryHeap(ComparableComparator.INSTANCE);
            m_deliverableHeapMap.put(sNodeName, heap);
         }
         else
         {
            return null;
         }
      }

      return heap;
   }

   /**
    * Pushes a message for delivery.
    *
    * @param nPriority the priority of the message.
    * @param nOrdinal the sequential position of the message.
    * @param sNodeName the node defining the distribution.
    * @param nDistribution the distribution category, one of the constants from
    *           ObjectDispatchListner: ANY - message must be processed by any
    *           one node. ALL - message must be processed by all nodes. ONLY -
    *           message must be processed by sNode. EXCEPT - message must be
    *           processed by all nodes except sNode. DISPATCHER - message must
    *           be processed synchronously by the dispatcher node.
    * @param message the message to deliver.
    */
   protected void pushMessage(int nPriority, int nOrdinal, String sNodeName, int nDistribution, DispatcherMessage message)
   {
      Delivery delivery = new Delivery(nPriority, nOrdinal, message, (nDistribution == ObjectDispatchListener.DISPATCHER));

      switch (nDistribution)
      {
         case ObjectDispatchListener.ANY:
            m_deliverableAnyHeap.add(delivery);

            break;
         case ObjectDispatchListener.DISPATCHER:
         case ObjectDispatchListener.ONLY:
         {
            Heap nodeHeap = getHeap(sNodeName);

            if (nodeHeap == null)
            {
               throw new UncheckedException("err.queueing.unknownNode", new Object[]
               {
                  sNodeName
               });
            }
            else
            {
               nodeHeap.add(delivery);
            }

            break;
         }
         case ObjectDispatchListener.ALL:
         case ObjectDispatchListener.EXCEPT:
            for (Lookup.Iterator iter = m_deliverableHeapMap.iterator(); iter.hasNext();)
            {
               String sCurrNode = (String)iter.next();

               if (nDistribution == ObjectDispatchListener.ALL || !sCurrNode.equals(sNodeName))
               {
                  ((Heap)iter.getValue()).add(delivery);
               }
            }

            break;
         default:
            throw new UncheckedException("err.queueing.invalidDistribution", new Object[]
            {
               Primitive.createInteger(nDistribution)
            });
      }
   }

   /**
    * Determines if a node is available as the destination for delivery.
    *
    * @param String sNode the node.
    * @return true if the node is available.
    */
   public boolean isAvailable(String sNodeName)
   {
      return sNodeName.equals(J2EEUtil.ISOLATED_NODE_NAME);
   }

   /**
    * Pulls the next message for delivery.
    *
    * @param sNodeName the node for which to pull a message.
    * @return Delivery the delivery, null if none are available.
    */
   protected Delivery pullMessage(String sNodeName)
   {
      if (!isAvailable(sNodeName))
      {
         m_deliverableHeapMap.remove(sNodeName);

         return new Delivery();
      }

      Delivery anyDelivery = (Delivery)m_deliverableAnyHeap.first();
      Delivery nodeDelivery = (Delivery)getHeap(sNodeName).first();
      Delivery delivery = anyDelivery;

      if (delivery == null || nodeDelivery != null && delivery.compareTo(nodeDelivery) > 0)
      {
         delivery = nodeDelivery;
      }

      if (delivery != null)
      {
         if (delivery == anyDelivery)
         {
            m_deliverableAnyHeap.removeFirst();
         }
         else
         {
            getHeap(sNodeName).removeFirst();
         }
      }

      return delivery;
   }

   /**
    * @see nexj.core.rpc.queueing.ObjectDispatchListener#onMessage(nexj.core.rpc.queueing.DispatcherMessage)
    */
   public void onMessage(DispatcherMessage message)
   {
      try
      {
         message.respond(onMessage(message.getType(), message.getArgArray(), message.getDispatcherName()));
      }
      catch (ShutdownException e)
      {
         message.error(DispatcherMessage.SHUTDOWN, e);
      }
      catch (InvalidDispatcherException e)
      {
         message.error(DispatcherMessage.DISPATCHER_CHANGED, e);
      }
      catch (ServerException e)
      {
         if (e.getCause() instanceof InvalidDispatcherException)
         {
            message.error(DispatcherMessage.DISPATCHER_CHANGED, e);
         }
         else
         {
            message.error(DispatcherMessage.EXCEPTION, e);
            ObjUtil.rethrow(e);
         }
      }
      catch (Throwable t)
      {
         message.error(DispatcherMessage.EXCEPTION, t);
         ObjUtil.rethrow(t);
      }
   }

   /**
    * @see nexj.core.rpc.queueing.ObjectDispatchListener#onMessage(int,
    *      java.lang.Object[])
    */
   protected Object onMessage(int nMessageType, Object[] args, String sDispatcherId)
   {
      switch (nMessageType)
      {
         // dispatcher node operations
         case DispatcherMessage.START_NODE:
            return startNode((Boolean)args[0]);

         case DispatcherMessage.DISPATCH:
            return dispatch(sDispatcherId);

         case DispatcherMessage.GET_MESSAGE:
            return getMessage((String)args[0], ((Number)args[1]).longValue(), sDispatcherId);

         case DispatcherMessage.POST: // post an non-persisted serializable message
            post((TransferObject)args[0], sDispatcherId);

            return null;

         case DispatcherMessage.REVOKE: // handle node unavailability
            revoke((List)args[0], (String)args[1]);

            return null;

            // client node operations
         case DispatcherMessage.RECEIVE: // receive a persisted message
            receive((Binary)args[0], (String)args[1], sDispatcherId);

            return null;

         case DispatcherMessage.EXECUTE: // receive an non-persisted message
            execute((TransferObject)args[0]);

            return null;

         default:
            throw new UncheckedException("err.queueing.invalidMethod", new Object[]
            {
               Primitive.createInteger(nMessageType)
            });
      }
   }

   /**
    * Notifies the dispatcher that a cluster node is no longer available.
    *
    * @param unavailableNodeList the list of unavailable nodes.
    * @param sDispatcherId the name of the dispatcher.
    */
   public void revoke(List unavailableNodeList, final String sDispatcherId)
   {
   }

   /**
    * Posts a command for delivery to one or several nodes.
    *
    * @param tobj the serializable message to post.
    */
   protected synchronized void post(final TransferObject tobj, final String sDispatcherId)
   {
      int nDistribution;
      String sHTTPNode = null;
      Object receiver = tobj.getValue(ObjectSender.RECEIVER);

      if (receiver == ONE_RECEIVER)
      {
         nDistribution = ANY;
      }
      else if (receiver == ALL_RECEIVERS)
      {
         nDistribution = ALL;
      }
      else if (receiver == DISPATCHER_RECEIVER)
      {
         nDistribution = DISPATCHER;
      }
      else if (receiver == OTHER_RECEIVERS)
      {
         nDistribution = EXCEPT;
         sHTTPNode = getNodeName();
      }
      else
      {
         nDistribution = ONLY;
         sHTTPNode = (String)receiver;
      }
     
      pushMessage(((Number)tobj.getValue(ObjectSender.PRIORITY)).intValue(), 0, (nDistribution == DISPATCHER) ? sDispatcherId : sHTTPNode, nDistribution,
         new DispatcherMessage()
         {
            public int getType()
            {
               return EXECUTE;
            }

            public Object[] getArgArray()
            {
               return new Object[]
               {
                  tobj
               };
            }

            public void respond(Object result)
            {
            }

            public void error(int nCause, Throwable t)
            {
            }

            public String getDispatcherName()
            {
               return sDispatcherId;
            }
         });

      notifyAll();
   }

   /**
    * If this node is the dispatcher, initialize the dispatcher. Else return the
    * address of the dispatcher node.
    *
    * @param bReset false if the dispatcher is not to be altered.
    * @return Object[] the array of cluster attributes, indexed by
    *         ObjectDispatchListener constants. Null unless running in
    *         distributed mode.
    */
   protected synchronized Object startNode(final Boolean bReset)
   {
      s_logger.debug("Initializing ObjectQueue dispatcher");

      final Object[] result = new Object[1];

      run(new ContextRunnable()
      {
         public void err(Throwable t, InvocationContext context) throws Throwable
         {
            throw new UncheckedException("err.queueing.startup", t);
         }

         public String getClientAddress() throws Throwable
         {
            return "dispatcher";
         }

         public String getUser() throws Throwable
         {
            return null;
         }

         public boolean isEnabled() throws Throwable
         {
            return true;
         }

         public void run(InvocationContext context) throws Throwable
         {
            context.setSecure(false);

            Metaclass dispatcherClass = m_metadata.getMetaclass("SysObjectQueueDispatcher");
            Instance dispatcher = (Instance)dispatcherClass.invoke("getDispatcher", new Object[]
            {
               Boolean.TRUE
            });

            m_sQueueName = (String)dispatcher.getValue("QUEUE_NAME");
            m_semaphoreMap = new HashTab();
            m_blockingSet = new HashHolder();
            m_nodeByProcessingMessageIdMap = new HashTab();
            m_processingMessagesByNodeNameMap = new HashTab();
            m_messageLoadAttributes = (Pair)dispatcher.getValue("SELECT_ATTRIBUTES", "LOAD_ATTRIBUTES");

            if (startNode(result, dispatcher, bReset, context))
            {
               s_logger.debug("This node selected to be dispatcher, recovering incomplete transactions");
               dispatcherClass.invoke("recover", new Object[]
               {
                  null
               });
            }
         }
      }, m_channel, "ObjectQueue");

      s_logger.dump("StartNode notifying all threads");
      notifyAll();

      if (result[0] == null)
      {
         return null;
      }

      return result[0];
   }

   /**
    * Initialize connection to the application cluster.
    *
    * @param startResult result value from the call to dispatcher.startNode()
    * @param dispatcher the dispatcher
    * @param bReset false if the dispatcher is not to be altered.
    * @param context the invocation context
    * @return true if this node is the dispatcher
    */
   protected boolean startNode(Object[] startResult, Instance dispatcher, Boolean bReset, InvocationContext context)
   {
      return true;
   }

   /**
    * Returns the message Id of a message cleared for delivery. If timeout
    * expires before a message becomes available, returns null.
    *
    * @param sCallingNode The name of the node requesting a message, may be null
    *           in a non-clustered environment.
    * @param lTimeout The timeout in milliseconds.
    * @return SysDispatcherMessage The message to be delivered to the calling
    *         node.
    */
   protected synchronized DispatcherMessage getMessage(final String sCallingNode, long lTimeout, final String sDispatcherId)
   {
      if (m_bShutdown)
      {
         throw new ShutdownException();
      }

      if (s_logger.isDebugEnabled())
      {
         s_logger.debug("Received getMessage request from node " + sCallingNode);
      }

      Delivery delivery = null;

      try
      {
         long lStartTime = System.currentTimeMillis();
         delivery = pullMessage(sCallingNode);

         // wait for deliverable messages
         while (delivery == null && !m_bShutdown)
         {
            if (m_bDispatch && m_tsNextDispatchTime == null)
            {
               // call to dispatch has been requested and has not yet been handled.
               run(new ContextRunnable()
               {
                  private Timestamp handleContext(InvocationContext context, Boolean safety)
                  {
                     context.setSecure(false);

                     Metaclass dispatcherClass = m_metadata.getMetaclass("SysObjectQueueDispatcher");

                     // verify that dispatcher remains the same
                     if (!sDispatcherId.equals(J2EEUtil.ISOLATED_NODE_NAME) &&
                        !sDispatcherId.equals(((Instance)((Instance)dispatcherClass.invoke("getDispatcher", new Object[]{Boolean.FALSE})).getValue("node")).getValue("name")))
                     {
                        throw new InvalidDispatcherException(sDispatcherId);
                     }

                     // dispatch in bulk
                     Pair result = (Pair)dispatcherClass.invoke("dispatch", new Object[]{safety});
                     InstanceList deliverable = (InstanceList)result.getTail();

                     if (deliverable != null && !deliverable.isEmpty())
                     {
                        for (int i = 0; i < deliverable.size(); i++)
                        {
                           final Instance message = deliverable.getInstance(i);
                           final Instance queue = (Instance)message.getValue("queue");
                           final String sQueueName = (String)queue.getValue("name");
                           Number priority = (Number)queue.getValue("priority");

                           pushMessage((priority == null) ? 1000 : priority.intValue(), i, sDispatcherId, (sQueueName
                              .equals(m_sQueueName)) ? ObjectDispatchListener.DISPATCHER : ObjectDispatchListener.ANY,
                              new DispatcherMessage()
                              {
                                 public int getType()
                                 {
                                    return RECEIVE;
                                 }

                                 public Object[] getArgArray()
                                 {
                                    return new Object[]
                                    {
                                       message.getOID().toBinary(),
                                       sQueueName
                                    };
                                 }

                                 public void error(int nCause, Throwable t)
                                 {
                                 }

                                 public void respond(Object result)
                                 {
                                 }

                                 public String getDispatcherName()
                                 {
                                    return sDispatcherId;
                                 }
                              });
                        }
                     }

                     if (s_logger.isDebugEnabled())
                     {
                        s_logger.debug("Dispatcher returned with " + ((deliverable == null) ? 0 : deliverable.size())
                           + " messages available for delivery");

                        if (s_logger.isDumpEnabled())
                        {
                           s_logger.dump("Dispatcher will run again at " + result.getHead());
                        }
                     }

                     s_logger.dump("GetMessage notifying all threads");
                     ObjectQueueDispatcher.this.notifyAll(); // alert calling
                                                             // nodes that new
                                                             // messages may be
                                                             // available.

                     return (Timestamp)result.getHead();
                  }

                  public void run(InvocationContext context) throws Throwable
                  {
                     m_tsNextDispatchTime = handleContext(context, Boolean.FALSE);
                  }

                  public boolean isEnabled() throws Throwable
                  {
                     return true;
                  }

                  public String getUser() throws Throwable
                  {
                     return null;
                  }

                  public String getClientAddress() throws Throwable
                  {
                     return "dispatcher";
                  }

                  public void err(Throwable t, InvocationContext context) throws Throwable
                  {
                     if (t instanceof InvalidDispatcherException)
                     {
                        throw t;
                     }

                     s_logger.debug("Dispatching in bulk failed, retrying with one transaction per operation", t);

                     context.initUnitOfWork();

                     try
                     {
                        m_tsNextDispatchTime = handleContext(context, Boolean.TRUE);

                        context.complete(true);
                     }
                     catch (Throwable tt)
                     {
                        context.complete(false);

                        throw tt;
                     }
                  }
               }, m_channel, "ObjectQueue");
            }

            delivery = pullMessage(sCallingNode);

            if (delivery == null)
            {
               if (lTimeout <= 0)
               {
                  break;
               }

               wait(lTimeout);
               s_logger.dump("GetMessage returned from wait");
               lTimeout = lTimeout + (lStartTime - System.currentTimeMillis());

               delivery = pullMessage(sCallingNode);
            }
         }

         if (m_bShutdown)
         {
            throw new ShutdownException();
         }

         if (delivery == null)
         {
            if (s_logger.isDumpEnabled())
            {
               s_logger.dump("No message ready within timeout, getMessage returning to node " + sCallingNode);
            }

            return null;
         }
      }
      catch (InterruptedException e)
      {
         if (s_logger.isDumpEnabled())
         {
            s_logger.dump("GetMessage interrupted, returning to node " + sCallingNode);
         }

         return null;
      }

      if (delivery.isUnavailable())
      {
         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("GetMessage returning because node " + sCallingNode + " is unavailable");
         }
      }
      else
      {
         if (delivery.m_bSynchronous)
         {
            // deliver in sequence with other dispatcher operations
            onMessage(delivery.m_message);
         }
         else
         {
            delivery.deliver(sCallingNode);

            return delivery.m_message;
         }
      }

      return null;
   }

   /**
    * Update the deliverable list and notify all waiting threads. Returns the
    * timestamp of the next anticipated change to the list.
    *
    * @return The timestamp of the next anticipated change to the list.
    */
   protected synchronized Timestamp dispatch(String sDispatcherId)
   {
      s_logger.debug("Dispatching");

      Timestamp nextDispatchTime = null;

      if (m_bDispatch)
      {
         // A dispatch request is already in progress. Return immediately,
         // leaving the original
         // request to pick up the next dispatch response.
         if (m_tsNextDispatchTime != null)
         {
            // the in-progress request is awake, but hasn't picked up the
            // response yet.
            nextDispatchTime = m_tsNextDispatchTime;
            // when the in-progress request sees null, it will re-notify all
            // waiting threads.
            m_tsNextDispatchTime = null;
         }
      }
      else
      {
         m_bDispatch = true;
         assert (m_tsNextDispatchTime == null);

         try
         {
            while (m_tsNextDispatchTime == null && !m_bShutdown)
            {
               s_logger.dump("Dispatch notifying all threads");
               notifyAll();
               wait();
               s_logger.dump("Dispatch returned from wait");
            }

            nextDispatchTime = m_tsNextDispatchTime;
            m_tsNextDispatchTime = null;
         }
         catch (InterruptedException e)
         {
            return null;
         }
         finally
         {
            m_bDispatch = false;
         }
      }

      if (m_bShutdown)
      {
         throw new ShutdownException();
      }

      return nextDispatchTime;
   }

   /**
    * Receive a message. Helper for receive and getMessage.
    *
    * @param context the context.
    * @param sQueueName the name of the queue.
    * @param channel the channel associated with the queue.
    * @param msg the messsage.
    * @param dispatcherClass the dispatcher class.
    */
   protected void receiveRun(InvocationContext context, String sQueueName, ObjectQueue channel, Instance msg,
      Metaclass dispatcherClass, String sDispatcherId)
   {
      if (s_logger.isDebugEnabled())
      {
         s_logger.debug("Receiving message with oid " + msg.getOID() + " from queue " + sQueueName
            + ((channel == null) ? "" : (" (" + channel.getName() + ")")) + " as user \"" + msg.getValue("user") + "\"");
      }

      // login the message sender
      context.login(new SimplePrincipal((String)msg.getValue("user")));

      // check that message has not been recovered by the dispatcher
      if (!((Boolean)msg.findValue("isProcessing", Boolean.TRUE)).booleanValue())
      {
         throw new InvalidDispatcherException(sDispatcherId);
      }

      // set unit of work timeout
      Number timeout = ((Number)((Instance)msg.getValue("queue")).getValue("timeout"));
      long lUnitOfWorkTimeout = (timeout == null) ? 0 : timeout.longValue();
      TransactionManager tm = (TransactionManager)m_metadata.getComponent("System.TransactionManager").getInstance(context);

      context.getUnitOfWork().setTimeout(lUnitOfWorkTimeout);

      try
      {
         TransferObject body = (TransferObject)msg.getValue("body");

         tm.setTransactionTimeout(86400); // 24 hours

         if (channel == null || body == null ||
            !((ObjectReceiver)channel.getReceiver().getInstance(context))
               .receiveServerMessage(context, msg, body))
         {
            s_logger.dump("Invoking receive");

            msg.invoke("receive");
         }
      }
      catch (SystemException e)
      {
         ObjUtil.rethrow(e);
      }

      if (!((Boolean)msg.getValue("commitRequired")).booleanValue())
      {
         // rollback everything except the message instance
         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("Message marked for rollback, rolling back:" + msg);
         }

         TransferObject detachedTobj = detachInstance(msg);

         context.complete(false);
         context.initUnitOfWork();

         // check that message has not been recovered by the dispatcher
         if (!((Boolean)msg.findValue("isProcessing", Boolean.TRUE)).booleanValue())
         {
            throw new InvalidDispatcherException(sDispatcherId);
         }

         attachInstance(detachedTobj, context);
      }

      boolean bSecured = context.isSecure();

      try
      {
         context.setSecure(false);
         dispatcherClass.invoke("clientComplete", new Object[] { msg });
      }
      finally
      {
         context.setSecure(bSecured);
      }
   }

   /**
    * Handles receive failure. Helper for receive and getMessage.
    *
    * @param context the invocation context.
    * @param t the failure.
    * @param oid the oid of the failed message.
    * @param sDispatcherId the id of the message's dispatcher.
    */
   protected void receiveErr(InvocationContext context, Throwable t, OID oid, String sDispatcherId) throws Throwable
   {
      if (t instanceof InvalidDispatcherException)
      {
         if (s_logger.isDebugEnabled())
         {
            s_logger.debug("Dispatcher changed during delivery of message with oid " + oid, t);
         }

         throw t;
      }

      if (s_logger.isDebugEnabled())
      {
         s_logger.debug("Delivery failure for message with oid " + oid + ", calling fail", t);
      }

      Metaclass dispatcherClass = m_metadata.getMetaclass("SysObjectQueueDispatcher");
      Instance msg = readMessage(oid, m_messageLoadAttributes, context);

      if (msg == null)
      {
         return; // message has been canceled. Unlikely, but possible.
      }

      // login the message sender
      context.login(new SimplePrincipal((String)msg.getValue("user")));

      // check that message has not been recovered by the dispatcher
      if (!((Boolean)msg.findValue("isProcessing", Boolean.TRUE)).booleanValue())
      {
         throw new InvalidDispatcherException(sDispatcherId);
      }

      // invoke fail method
      msg.invoke("fail", new Object[] {t});

      boolean bSecured = context.isSecure();

      try
      {
         context.setSecure(false);
         dispatcherClass.invoke("clientComplete", new Object[] {msg});
      }
      finally
      {
         context.setSecure(bSecured);
      }
   }

   /**
    * Blacklist a message.
    *
    * @param sDispatcherId The id of the dispatcher at the time the message was
    *           marked for processing
    * @param context The invocation context.
    * @param oid The oid of the message to blacklist.
    * @param t The exception leading to the blacklist.
    */
   protected void blacklist(String sDispatcherId, InvocationContext context, OID oid, Throwable t) throws Throwable
   {
      if (s_logger.isWarnEnabled())
      {
         s_logger.warn("Delivery failed for message with oid " + oid + ", blacklisting message", t);
      }

      context.initUnitOfWork();

      boolean bSecured = context.isSecure();

      try
      {
         Metaclass dispatcherClass = m_metadata.getMetaclass("SysObjectQueueDispatcher");
         InstanceList messages = Query.createRead(m_metadata.getMetaclass("SysMessage"), m_messageLoadAttributes, Pair.attribute("").eq(oid),
            null, -1, 0, false, Query.SEC_NONE, context).read();

         Instance msg = (messages == null || messages.isEmpty()) ? null : messages.getInstance(0);

         if (msg == null)
         {
            return; // message doesn't exist, can't blacklist it.
         }

         // check that message has not been recovered by the dispatcher
         if (!((Boolean)msg.findValue("isProcessing", Boolean.TRUE)).booleanValue())
         {
            throw new InvalidDispatcherException(sDispatcherId);
         }

         context.setSecure(false);

         // mark message as undeliverable.
         dispatcherClass.invoke("blacklist", new Object[]
         {
            msg
         });

         context.complete(true);
      }
      catch (Throwable tt)
      {
         context.complete(false);

         throw tt;
      }
      finally
      {
         context.setSecure(bSecured);
      }
   }

   /**
    * Receives and a non-persisted message on the client node.
    *
    * @param tobj the message.
    */
   protected void execute(final TransferObject tobj)
   {
      final String sQueueName = (String)tobj.getValue(ObjectSender.CHANNEL);
      final Channel channel = m_metadata.getChannel(sQueueName);

      run(new ContextRunnable()
      {
         public void err(Throwable t, InvocationContext context) throws Throwable
         {
            s_logger.error("Failed to receive non-persisted message " + tobj, t);
         }

         public String getClientAddress() throws Throwable
         {
            return sQueueName;
         }

         public String getUser() throws Throwable
         {
            return (String)tobj.getValue(ObjectSender.USER);
         }

         public boolean isEnabled() throws Throwable
         {
            return true;
         }

         public void run(InvocationContext context) throws Throwable
         {
            // set unit of work timeout
            Number timeout = ((Number)((Instance)m_metadata.getMetaclass("SysQueue")
               .invoke("getQueue", new Object[] {sQueueName})).getValue("timeout"));
            long lUnitOfWorkTimeout = (timeout == null) ? 0 : timeout.longValue();
            TransactionManager tm = (TransactionManager)m_metadata.getComponent("System.TransactionManager").getInstance(context);

            context.getUnitOfWork().setTimeout(lUnitOfWorkTimeout);

            try
            {
               tm.setTransactionTimeout(86400); // 24 hours
               ((ObjectReceiver)channel.getReceiver().getInstance(context)).receiveServerMessage(context, null, tobj);
            }
            catch (SystemException e)
            {
               ObjUtil.rethrow(e);
            }
         }
      }, channel, "ObjectQueue");
   }

   /**
    * Reads a message instance.
    *
    * @param oid the id of the message.
    * @param attributes the message attributes to load.
    * @param context the invocation context.
    * @return the message, null if not found.
    */
   protected Instance readMessage(OID oid, Pair attributes, InvocationContext context)
   {
      InstanceList messages = Query.createRead(m_metadata.getMetaclass("SysMessage"), attributes, Pair.attribute("").eq(oid),
         null, -1, 0, false, Query.SEC_NONE, context).read();

      Instance message = (messages == null || messages.isEmpty()) ? null : messages.getInstance(0);

      if (message != null)
      {
         boolean bWasSecured = context.isSecure();

         context.setSecure(false);

         try
         {
            message.setValue("updateEnabled", Boolean.TRUE);
         }
         finally
         {
            context.setSecure(bWasSecured);
         }
      }

      return message;
   }

   /**
    * Receives a message on a client node.
    *
    * @param id the id of the message to receive.
    * @param sQueueName the name of the queue from which the message is
    *           delivered.
    */
   protected void receive(final Binary id, final String sQueueName, final String sDispatcherId)
   {
      Channel candidateChannel = m_metadata.findChannel(sQueueName);
      final ObjectQueue channel = (candidateChannel instanceof ObjectQueue) ? (ObjectQueue)candidateChannel : null;
      final OID oid = OID.fromBinary(id);

      run(new ContextRunnable()
      {
         public void run(InvocationContext context) throws Throwable
         {
            Instance msg = readMessage(oid, Pair.append(RECEIVE_ATTRIBUTES, m_messageLoadAttributes), context);

            if (msg == null)
            {
               throw new UncheckedException("err.queueing.receive", new Object[]
               {
                  oid
               });
            }

            Metaclass dispatcherClass = m_metadata.getMetaclass("SysObjectQueueDispatcher");
            Channel candidateChannel = m_metadata.findChannel(sQueueName);
            final ObjectQueue channel = (candidateChannel instanceof ObjectQueue) ? (ObjectQueue)candidateChannel : null;

            try
            {
               receiveRun(context, sQueueName, channel, msg, dispatcherClass, sDispatcherId);
            }
            catch (Throwable t)
            {
               context.complete(false);
               context.initUnitOfWork();
               receiveErr(context, t, oid, sDispatcherId);
            }
         }

         public boolean isEnabled() throws Throwable
         {
            return true;
         }

         public String getUser() throws Throwable
         {
            return null;
         }

         public String getClientAddress() throws Throwable
         {
            return sQueueName;
         }

         public void err(Throwable t, InvocationContext context) throws Throwable
         {
            if (!(t instanceof InvalidDispatcherException))
            {
               blacklist(sDispatcherId, context, oid, t);
            }
         }
      }, (channel == null) ? m_channel : channel, "ObjectQueue");
   }

   /**
    * Acquire a semaphore. Note: not thread-safe. Assumes caller is the single
    * dispatcher thread.
    *
    * @param semaphoreDef The (resource . maxCount) pair defining the semaphore.
    * @return true if the semaphore is acquired, false otherwise.
    */
   public static boolean acquireSemaphore(Metaclass metaclass, ObjectQueueDispatcher comp, Pair semaphoreDef, ActionContext actx)
   {
      return comp.acquire(semaphoreDef);
   }

   /**
    * @deprecated Acquire a semaphore. Note: not thread-safe. Assumes caller is
    *             the single dispatcher thread.
    * @param semaphoreDef The (resource . maxCount) pair defining the semaphore.
    * @return true if the semaphore is acquired, false otherwise.
    */
   public static Boolean acquireSemaphore(Metaclass metaclass, InvocationContext context, ObjectQueueDispatcher comp,
      Pair semaphoreDef, ActionContext actx)
   {
      return Boolean.valueOf(acquireSemaphore(metaclass, comp, semaphoreDef, actx));
   }

   /**
    * Release a semaphore on commit. Note: not thread-safe. Assumes caller is
    * the single dispatcher thread.
    *
    * @param semaphoreDef The (resource . maxCount) pair defining the semaphore.
    */
   public static void releaseSemaphore(Metaclass metaclass, ObjectQueueDispatcher comp, Pair semaphoreDef, ActionContext actx)
   {
      comp.release(semaphoreDef);
   }

   /**
    * @ deprecated Release a semaphore on commit. Note: not thread-safe. Assumes
    * caller is the single dispatcher thread.
    *
    * @param semaphoreDef The (resource . maxCount) pair defining the semaphore.
    */
   public static void releaseSemaphore(Metaclass metaclass, InvocationContext context, ObjectQueueDispatcher comp,
      Pair semaphoreDef, ActionContext actx)
   {
      releaseSemaphore(metaclass, comp, semaphoreDef, actx);
   }

   /**
    * Increments a semaphore.
    *
    * @param resource The resource for which to increment the semaphore.
    * @param maxCount The maximum count for the resource. null leave maximum
    *           unchanged, and to ignore the maximum for this operation only.
    * @return true if the semaphore is acquired, false otherwise.
    */
   private boolean incrementSemaphore(Binary resource, Number maxCount)
   {
      Semaphore semaphore = (Semaphore)m_semaphoreMap.get(resource);

      if (semaphore == null)
      {
         semaphore = new Semaphore();
         m_semaphoreMap.put(resource, semaphore);
      }

      boolean bAcquired = semaphore.acquire(maxCount);

      if (s_logger.isDumpEnabled())
      {
         s_logger.dump((bAcquired) ? "Acquired" : "Not acquired");
      }

      if (bAcquired && semaphore.isSaturated())
      {
         m_blockingSet.add(semaphore);
      }

      return bAcquired;
   }

   /**
    * Acquire a semaphore. Note: not thread-safe. Assumes caller is the single
    * dispatcher thread.
    *
    * @param semaphoreDef The (resource . maxCount) pair defining the semaphore.
    * @return true if the semaphore is acquired, false otherwise.
    */
   protected boolean acquire(Pair semaphoreDef)
   {
      if (s_logger.isDumpEnabled())
      {
         s_logger.dump("Acquiring semaphore " + semaphoreDef);
      }

      final Binary resource = (Binary)semaphoreDef.getHead();
      boolean bAcquired = incrementSemaphore(resource, (Number)semaphoreDef.getTail());

      if (s_logger.isDumpEnabled())
      {
         s_logger.dump((bAcquired) ? "Acquired" : "Not acquired");
      }

      if (bAcquired)
      {
         Function compensator = new Function()
         {
            public boolean invoke(int nArgCount, Machine machine)
            {
               int nTxState = ((Number)machine.getArg(nArgCount - 1, nArgCount)).intValue();

               if (nTxState == Status.STATUS_ROLLEDBACK)
               {
                  release(resource);
               }

               return false;
            }
         };

         // if transaction rolls back, rollback the acquisition.
         ((InvocationContext)ThreadContextHolder.getContext()).getUnitOfWork().addCompensator(compensator, compensator);
      }

      return bAcquired;
   }

   /**
    * Release the semaphore protecting resource.
    *
    * @param resource The resource of the semaphore to release.
    */
   private void release(Binary resource)
   {
      Semaphore semaphore = (Semaphore)m_semaphoreMap.get(resource);

      if (semaphore != null)
      {
         if (s_logger.isDumpEnabled())
         {
            s_logger.dump("Releasing semaphore " + resource);
         }

         if (semaphore.release())
         {
            m_semaphoreMap.remove(resource);
         }

         if (!semaphore.isSaturated())
         {
            m_blockingSet.remove(semaphore);
         }
      }
   }

   /**
    * Release a semaphore on commit. Note: not thread-safe. Assumes caller is
    * the single dispatcher thread.
    *
    * @param semaphoreDef The (resource . maxCount) pair defining the semaphore.
    */
   protected void release(Pair semaphoreDef)
   {
      final Binary resource = (Binary)semaphoreDef.getHead();

      release(resource); // release the resource immediately

      Function compensator = new Function()
      {
         public boolean invoke(int nArgCount, Machine machine)
         {
            int nTxState = ((Number)machine.getArg(nArgCount - 1, nArgCount)).intValue();

            if (nTxState == Status.STATUS_ROLLEDBACK)
            {
               // release will be called for the resource again in a future
               // transaction, so we need to reset the resource count.
               incrementSemaphore(resource, null);
            }

            return false;
         }
      };

      ((InvocationContext)ThreadContextHolder.getContext()).getUnitOfWork().addCompensator(compensator, compensator);
   }

   /**
    * Complete a message delivery.
    *
    * @param OID oid the oid of the message that has been delivered.
    */
   public static void complete(Metaclass metaclass, final ObjectQueueDispatcher comp, final OID oid, ActionContext actx)
   {
      Function compensator = new Function()
      {
         public boolean invoke(int nArgCount, Machine machine)
         {
            int nTxState = ((Number)machine.getArg(nArgCount - 1, nArgCount)).intValue();

            if (nTxState == Status.STATUS_COMMITTED)
            {
               comp.complete(oid);
            }

            return false;
         }
      };

      ((InvocationContext)ThreadContextHolder.getContext()).getUnitOfWork().addCompensator(compensator, compensator);
   }

   /**
    * @param deliveryTime The delivery time for which
    * @return message ordinal that is larger than all previous ordinals for the
    *         given deliveryTime.
    */
   public static int getMessageOrdinal(Metaclass metaclass, Timestamp deliveryTime, ActionContext actx)
   {
      if (!deliveryTime.equals(s_tsLastTimestamp))
      {
         s_tsLastTimestamp = deliveryTime;
         s_nOrdinal = 0;
      }
      else
      {
         s_nOrdinal++;
      }

      return s_nOrdinal;
   }

   /**
    * @deprecated Complete a message delivery.
    * @param OID oid the oid of the message that has been delivered.
    */
   public static void complete(Metaclass metaclass, InvocationContext context, final ObjectQueueDispatcher comp, final OID oid,
      ActionContext actx)
   {
      complete(metaclass, comp, oid, actx);
   }

   /**
    * Complete a message delivery.
    *
    * @param OID oid the oid of the message that has been delivered.
    */
   public void complete(OID oid)
   {
      Binary binOID = oid.toBinary();
      String sTransactionNode = (String)m_nodeByProcessingMessageIdMap.get(binOID);

      if (sTransactionNode != null)
      {
         Set transactionSet = (Set)m_processingMessagesByNodeNameMap.get(sTransactionNode);

         if (transactionSet != null)
         {
            transactionSet.remove(binOID);
         }

         m_nodeByProcessingMessageIdMap.remove(sTransactionNode);
      }
   }

   /**
    * Return a transfer object corresponding to instance, and mark instance
    * clean.
    *
    * @param Instance instance The instance to detach from the current unit of
    *           work.
    * @return A transfer object corresponding to instance.
    */
   public TransferObject detachInstance(Instance instance)
   {
      Pair attributes = null;

      for (Iterator iter = instance.getMetaclass().getInstanceAttributeIterator(); iter.hasNext();)
      {
         Attribute attribute = (Attribute)iter.next();

         if (instance.isUpdateable(attribute) && instance.isDirty(attribute.getOrdinal()))
         {
            attributes = new Pair(attribute.getSymbol(), attributes);
         }
      }

      TransferObject tobj = (TransferObject)RPCUtil.transfer(instance, attributes, RPCUtil.TF_ALL);

      if (s_logger.isDumpEnabled())
      {
         s_logger.dump("Detached instance " + instance + " from unit of work, yielding " + tobj);
      }

      return tobj;
   }

   /**
    * Instantiate an instance previously detached by detachInstance.
    *
    * @param TransferObject tobj the transfer object to instantiate.
    * @param InvocationContext context the context in which to instantiate the
    *           instance.
    * @return The newly instantiated instance.
    */
   public Instance attachInstance(TransferObject tobj, InvocationContext context)
   {
      boolean bSecured = context.isSecure();
      Instance inst = null;

      try
      {
         context.setSecure(false);
         InstanceFactory instanceFactory = new InstanceFactory(new HashTab(), new ArrayList(), InstanceFactory.STATE, context);

         inst = instanceFactory.instantiate(tobj);

         instanceFactory.complete();
      }
      finally
      {
         context.setSecure(bSecured);
      }

      if (s_logger.isDumpEnabled())
      {
         s_logger.dump("Attached instance " + inst + " to unit of work, from " + tobj);
      }

      return inst;
   }

   /**
    * @return Set of resources that have saturated semaphores.
    */
   public Set getBlockingSet()
   {
      return m_blockingSet;
   }

   /**
    * Sets the invocation context component.
    *
    * @param contextComponent The invocation context component to set.
    */
   public void setContextComponent(Component contextComponent)
   {
      m_contextComponent = contextComponent;
   }

   /**
    * Sets the metadata.
    *
    * @param metadata The metadata.
    */
   public void setMetadata(Metadata metadata)
   {
      m_metadata = metadata;
   }

   /**
    * Sets the enabled flag.
    *
    * @param enabled The enabled flag.
    */
   public void setEnabled(boolean bEnabled)
   {
      m_bEnabled = bEnabled;
   }

   /**
    * @return true if the dispatcher is enabled.
    */
   public boolean isEnabled()
   {
      return m_bEnabled;
   }

   /**
    * @see nexj.core.util.Lifecycle#shutdown()
    */
   public synchronized void shutdown()
   {
      s_logger.info("Stopping ObjectQueueDispatcher");
      m_bShutdown = true;
      s_logger.dump("Shutdown notifying all threads");
      notifyAll();
   }

   /**
    * @see nexj.core.util.Lifecycle#startup()
    */
   public synchronized void startup() throws Exception
   {
      if (m_metadata == null)
      {
         // support older metadata that did not initialize through component
         // definition
         m_metadata = Repository.getMetadata();
      }

      m_channel = (ObjectDispatcherQueue)m_metadata.getChannel("SysObjectQueueDispatcher");
      m_bShutdown = !m_bEnabled;
   }

   /**
    * @see nexj.core.util.Suspendable#resume()
    */
   public void resume() throws Exception
   {
      startup();
   }

   /**
    * @see nexj.core.util.Suspendable#suspend()
    */
   public void suspend() throws Exception
   {
      shutdown();
   }

   // inner classes

   /**
    * A semaphore protects a resource accessible by a limited number of
    * concurrent threads.
    */
   private class Semaphore
   {
      /**
       * The maximum concurrency count.
       */
      public int m_nMaxCount;

      /**
       * The current concurrency count.
       */
      public int m_nCount;

      /**
       * Acquire the semaphore.
       *
       * @param maxCount The maximum count of the semaphore.
       * @return true if the semaphore is acquired, false otherwise.
       */
      public boolean acquire(Number maxCount)
      {
         if (maxCount == null)
         {
            m_nCount++;

            return true;
         }

         m_nMaxCount = maxCount.intValue();

         if (m_nCount < m_nMaxCount)
         {
            m_nCount++;

            return true;
         }

         return false;
      }

      /**
       * @return true If acquire(m_nMaxCount) will fail.
       */
      public boolean isSaturated()
      {
         return m_nCount >= m_nMaxCount;
      }

      /**
       * Releases the semaphore, return true if this is no longer held by any
       * thread.
       */
      public boolean release()
      {
         m_nCount--;

         return m_nCount <= 0;
      }
   }

   /**
    * Class to associate a command with a priority.
    */
   private class Delivery implements Comparable
   {
      public DispatcherMessage m_message;

      public int m_nPriority;

      public int m_nOrdinal;

      public boolean m_bSynchronous;

      public Delivery()
      {
      }

      public Delivery(int nPriority, int nOrdinal, DispatcherMessage message, boolean bSynchronous)
      {
         m_message = message;
         m_nPriority = nPriority;
         m_nOrdinal = nOrdinal;
         m_bSynchronous = bSynchronous;
      }

      public boolean isUnavailable()
      {
         return m_message == null;
      }

      /**
       * Initiate delivery of message to a node.
       *
       * @param sNode the node to which to deliver.
       */
      public void deliver(String sNode)
      {
         if (m_message.getType() != DispatcherMessage.RECEIVE)
         {
            if (s_logger.isDebugEnabled())
            {
               s_logger.debug("GetMessage delivering non-persisted message to node " + sNode);
            }
         }
         else
         {
            Binary binOID = (Binary)m_message.getArgArray()[0];

            if (s_logger.isDebugEnabled())
            {
               s_logger.debug("GetMessage delivering oid " + binOID + " to node " + sNode);
            }

            // record transaction, so we can track the most recent node to
            // operate on the message.
            m_nodeByProcessingMessageIdMap.put(binOID, sNode);

            if (sNode != null)
            {
               Set transactionSet = (Set)m_processingMessagesByNodeNameMap.get(sNode);

               if (transactionSet == null)
               {
                  transactionSet = new HashHolder(1);
                  m_processingMessagesByNodeNameMap.put(sNode, transactionSet);
               }

               transactionSet.add(binOID);
            }
         }
      }

      /**
       * @see java.lang.Comparable#compareTo(java.lang.Object)
       */
      public int compareTo(Object req)
      {
         int nOtherPriority = ((Delivery)req).m_nPriority;

         if (m_nPriority < nOtherPriority)
         {
            return -1;
         }

         if (m_nPriority > nOtherPriority)
         {
            return 1;
         }

         int nOtherOrdinal = ((Delivery)req).m_nOrdinal;

         if (m_nOrdinal < nOtherOrdinal)
         {
            return -1;
         }

         if (m_nOrdinal > nOtherOrdinal)
         {
            return 1;
         }

         return 0;
      }
   }
}
TOP

Related Classes of nexj.core.rpc.queueing.ObjectQueueDispatcher$Delivery

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.