Dispatcher.java

/*
** Module   : Dispatcher.java
** Abstract : inbound message processor
**
** Copyright (c) 2005-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------Description------------------------------------------------------
** 001 SIY 20050121   @19494 Created version 1.0.
** 002 SIY 20050210   @19770 Extract original exception if it is
**                           available.
** 003 NVS 20050228   @20109 Enabled SecurityManager calls in Dispatcher.
**                           They are about security context switching.
**                           Many of these are restricted use methods, so
**                           the proper call/method information is
**                           gathered and built into SecurityManager.java.
** 004 SIY 20050308   @20241 Changed a lot of small typos.
** 005 NVS 20050314   @20319 Security context switch methods now can throw
**                           RestrictedUseException. It is handled now.
** 006 SIY 20050329   @20542 Fixed comments and added stop() method.
** 007 NVS 20050331   @20559 stop() method that is used to shutdown the
**                           server is protected with access rights check.
**                           The calling subject has to have enough rights
**                           to access "shutdown" instance of "system"
**                           abstract resource in SYSTEM_SHUTDOWN_ACCESS
**                           mode.
** 008 NVS 20050414   @20722 Secured generateRoutingKey and getRoutingKey
**                           methods with net ACL checks for WRITE and
**                           READ access respectively. Secured
**                           addMethodImpl() for WRITE access and
**                           dispatch() for EXECUTE access.
** 009 NVS 20050415   @20739 Implemented mapping of special internal
**                           negative EIR indices into "system" group API
**                           names so that they can be protected with ACL.
** 010 NVS 20050505   @21112 Permanent dispatcher threads now repeatedly
**                           call setInitialSecurityContext() in their
**                           main loop to help refresh the security cache.
** 011 NVS 20060208   @24473 Added control flow calls and INTERRUPT call
**                           handling.
** 012 NVS 20060213   @24572 Fixed asynchronous interrupt request handler.
** 013 NVS 20060614   @27194 Added new method isUp() that checks to see if
**                           it's safe to call other Dispatcher methods.
** 014 GES 20060721   @28151 Added logging of calls to exported methods
**                           at FINER level and parameter values/return
**                           value and elapsed millis at FINEST level.
**                           Many formatting and code standards cleanups.
** 015 GES 20060728   @28208 Reworked dispatch() and code called from
**                           there to eliminate any processing that is
**                           not needed, including extra calls to obtain
**                           context local data.
** 016 NVS 20060817   @28643 Suppressed unneeded stack trace output when
**                           dispatcher threads are stopped due to the
**                           server shutdown.
** 017 NVS 20061017   @30463 Implemenented ability to restart the session
**                           without exiting the process.
** 018 GES 20061019   @30538 Added a return value to interruptSession().
**                           Reworked dispatch() to allow the use of
**                           push/popUnconditionally() to honor any
**                           pending interrupt.  Also we now avoid these
**                           push/pop calls in the case of an asynch
**                           interruption request (INTERRUPT_SESSION).
** 019 GES 20061212   @31665 Added some safety code to ensure that we
**                           never try to forward a response using a
**                           dead queue.
** 020 GES 20070111   @31785 Cleanup and removal of dead code. Fixed
**                           some security issues and matched interface
**                           changes as needed.
** 021 GES 20070115   @31831 Refactored core inbound request processing
**                           into a separate method so that it can be
**                           used in conversation mode.
** 022 GES 20070410   @32915 Fix for a case where honoring an interrupt
**                           in pushUnconditionally() would corrupt the
**                           state of the control stack. Added global
**                           locking support during interrupt requests
**                           so that a potential race condition can't
**                           occur (where the interrupted thread calls
**                           over to the peer before the peer can get
**                           the reply that tells it whether the interrupt
**                           was delivered or not).
** 023 NVS 20070411   @32928 Dispatcher threads are created as part of a
**                           thread group named "dispatcher".
** 024 ECF 20071101   @35879 Refactored net package. Removed all context
**                           management to SessionManager/Session classes.
**                           Implemented generics. Moved remote
**                           authentication/termination to SessionManager.
**                           Other minor cleanup.
** 025 ECF 20071127   @36048 Removed remaining SecurityManager references.
**                           Replaced with SessionManager. Ensured an
**                           initial security context is set before any
**                           security plugin instances are retrieved.
** 026 SVL 20090715   @43196 Added OOME protection into message processing
**                           thread.
** 027 GES 20090722   @43333 Better thread names to enhance debugging.
** 028 NVS 20090816   @43642 Hardening the dispatch() method to errors and
**                           exceptions (it shouldn't exit).
** 029 CA  20090831   @43794 Fixed interruption processing when both peers
**                           are in remote mode. When sending a request, the
**                           ID of the request is passed to 
**                           ctrl.pushUnconditionally, to be pushed on the  
**                           Control.operations stack. interruptSession was
**                           modified to receive data about the remote 
**                           operation executed when the interruption was
**                           encountered. Fixed dispatcher thread close on 
**                           client restart.
** 030 CA  20090901   @43811 Changes related to virtual control multiplexing.
** 031 CA  20100318   @44761 processInbound must unlock control if it acquired
**                           it, regardless of the queue's state (running or 
**                           not).
** 032 GES 20111025          Reworked method export processing to allow the
**                           secure replacement of exports by correlating the
**                           original export caller with the subsequent caller
**                           using a unique token for "authentication". This
**                           feature is needed for client-side exports when
**                           the client code is running in the server process
**                           and a CTRL-C causes the client to restart. The
**                           exported APIs must be remapped to new instances
**                           of the client objects.
** 033 CA  20111209          Added methods to remove a registered API. Must be
**                           used only for static APIs.
** 034 CA  20130822          Fixed the net protocol to support async requests.
** 035 CA  20130913          A dispatcher thread handling an async request must be registered as
**                           async thread, for the duration of this request.
** 036 CA  20131121          The sync'ed state processing is moved from Reader/Writer threads to 
**                           the thread which actually sends/receives the message (i.e. 
**                           Conversation thread or Dispatcher thread).
** 037 GES 20150923          Added logging for unexpected throwables during inbound processing.
** 038 GES 20151002          Added another excluded exception to the criteria list.
** 039 IAS 20160805          getMethod(RoutingKey) optimization.
** 040 VVT 20200203          Extra items removed from the method 'throws' list.
** 041 CA  20201202          State-sync via applyChanges() must be performed just before the method is invoked.
**                           This will allow any exception thrown by this to be properly caught and sent to
**                           the caller.  Also, the 'catch' block was changed to allow catching any Throwable.
**                           In case of a non-Exception, this will be wrapped in a RuntimeException so that
**                           the caller can handle it like an Exception.
**     CA  20201203          Moved the 'applyChanges' to the previous place, while not allowing the message
**                           to execute in case of errors (and report this back to the caller).
**     TJD 20220504          Java 11 compatibility minor changes
** 042 VVT 20230318          Cosmetic changes, unnecessary casts removed.
** 043 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 044 GBB 20240607          Tracing logging flag removed, replaced by isLoggable.
** 045 ES  20240619          Added serverInterrupt method to insert Ctrl-C into TypeAhead queue.
**     ES  20240619          Added serverInterrupt method to trigger a interrupt caused by stop-after timeout.
** 046 ICP 20240704          Fixed processInbound: Moved trackRequest to occur after context setting.
*      ICP 20240705          Fixed method of obtaining the class loader.
*/

/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/

package com.goldencode.p2j.net;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.logging.*;

import com.goldencode.p2j.classloader.MultiClassLoader;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;

/**
 * This class represents an incoming request processor. It contains pool of
 * processing threads, Exported Interfaces Registry (EIR) and Security Context
 * Map (SCM).
 */
public final class Dispatcher
implements MessageTypes
{
   /** Thread group for the dispatcher threads. */
   static final ThreadGroup dispThreads = new ThreadGroup(
      Thread.currentThread().getThreadGroup(), "dispatcher");
   
   /** Logger (safe as a JVM-wide value rather than as context-local). */
   private static final CentralLogger LOG =
      CentralLogger.get(Dispatcher.class.getName());
   
   /** List of throwables to exclude from logging. */
   private static final Class[] criteria = new Class[]
   {
      ConditionException.class,
      InterruptedException.class,
      UnstoppableExitException.class,
      EOFException.class
   };
      
   /** Session manager (safe as a JVM-wide value). */
   private static SessionManager sessMgr = null;
         
   /** Dispatcher shared instance. */
   private static Dispatcher dispatcher = null;
   
   /**
    * This variable is a part of EIR (Exported Interface Registry) and
    * contains names of groups and methods.
    */
   private final Vector<KeyMapEntry> keyMap;
   
   /** Map routing key to method */
   private final ConcurrentMap<RoutingKey, Method> methodsByKey = new ConcurrentHashMap<>();

   /**
    * This variable is a part of EIR (Exported Interface Registry) and
    * contains mapping between instances of RoutingKey and methods.
    */
   private final HashMap<RoutingKey, MethodInvoker> methodMap;
   
   /** Token which authenticates the caller as allowed to modify the EIR. */
   private final HashMap<Object, Set<RoutingKey>> modTokens;

   /** A map containing semaphores for each static exported method. */
   private final Map<Method, Semaphore> staticMethodLocks = 
      new HashMap<Method, Semaphore>();

   /** Dispatcher thread pool array. */
   private Thread[] threads;
   
   /** Flag indicating the dispatcher is closing down. */
   private boolean closing = false;

   /**
    * Constructs an instance of <code>Dispatcher</code>, including
    * initialization of thread pool.
    * <p>
    * Enables tracing of method invocations at logging level 
    * <code>Level.FINER</code>.  Enables tracing of parameters/result values
    * and elapsed time in milliseconds at logging level
    * <code>Level.FINEST</code>.
    * 
    * @param    num
    *           Number of threads in the thread pool.
    */
   private Dispatcher(int num)
   {
      keyMap    = new Vector<KeyMapEntry>();
      methodMap = new HashMap<RoutingKey, MethodInvoker>();
      modTokens = new HashMap<Object, Set<RoutingKey>>();
      
      // at least two threads should be running
      if (num <= 0)
         num = 2;

      threads = new Thread[num];

      int i;

      for (i = 0; i < threads.length; i++)
      {
         threads[i] = new Thread(dispThreads, new DispatcherStub());
         threads[i].setName(String.format("Dispatcher %d", i));
         threads[i].setDaemon(true);
      }

      // start thread pool
      for (i = 0; i < threads.length; i++)
      {
         threads[i].start();
      }
   }

   /**
    * Add a method to the Exported Interface Registry (EIR). The instance of
    * <code>Method</code> is provided by the application. If a method with
    * the specified key is already present in the EIR it is only replaced if
    * the previously returned authentication token is given here, otherwise a
    * <code>SecurityException</code> is thrown.
    * 
    * @param    key
    *           RoutingKey instance which is used as a key for adding method.
    * @param    obj
    *           Reference to the instance of the object which will handle the
    *           requests. Should be <code>null</code> for static methods.
    * @param    method
    *           Instance of <code>java.lang.reflect.Method</code> which will
    *           handle requests.
    * @param    modToken
    *           The modification token retured from the original registration
    *           call (to this same method). May be <code>null</code> on the
    *           first call for this routing key (a new token will be created
    *           and returned in this case). If <code>non-null</code> on the
    *           first call, that object will be used as the modification token
    *           from then on.
    *
    * @return   The token used to authenticate the caller when a modification
    *           is requested.
    *
    * @throws   SecurityException
    *           is forwarded from <code>addMethodImpl</code> method.
    */
   public static Object addMethod(RoutingKey key,
                                  Object     obj,
                                  Method     method,
                                  Object     modToken)
   throws SecurityException
   {
      return dispatcher.addMethodImpl(key, obj, method, modToken);
   }

   /**
    * Add a method to the Exported Interface Registry (EIR). The instance of
    * <code>Method</code> is provided by the application. If a method with
    * the specified key is already present in the EIR it is only replaced if
    * the previously returned authentication token is given here, otherwise a
    * <code>SecurityException</code> is thrown.
    * 
    * @param    key
    *           RoutingKey instance which is used as a key for adding method.
    * @param    obj
    *           Reference to the instance of the object which will handle the
    *           requests. Should be <code>null</code> for static methods.
    * @param    name
    *           Name of the method in the target class
    * @param    signature
    *           List of types of parameters of the method in the target class.
    * @param    cls
    *           Target class
    * @param    modToken
    *           The modification token retured from the original registration
    *           call (to this same method). May be <code>null</code> on the
    *           first call for this routing key (a new token will be created
    *           and returned in this case). If <code>non-null</code> on the
    *           first call, that object will be used as the modification token
    *           from then on.
    *
    * @return   The token used to authenticate the caller when a modification
    *           is requested.
    *
    * @throws   NoSuchMethodException
    *           is forwarded from <code>Class.getMethod</code> method.
    * @throws   SecurityException
    *           is forwarded from <code>Class.getMethod</code> or
    *           <code>addMethodImpl</code> methods.
    */
   public static Object addMethod(RoutingKey key,
                                  Object     obj,
                                  String     name,
                                  Class[]    signature,
                                  Class      cls,
                                  Object     modToken)
   throws NoSuchMethodException,
          SecurityException
   {
      Method method = cls.getMethod(name, signature);
      return dispatcher.addMethodImpl(key, obj, method, modToken);
   }

   /**
    * Add a method to the Exported Interface Registry (EIR). The instance of
    * <code>Method</code> is provided by the application. If a method with
    * the specified key is already present in the EIR it is only replaced if
    * the previously returned authentication token is given here, otherwise a
    * <code>SecurityException</code> is thrown.
    * 
    * @param    key
    *           RoutingKey instance which is used as a key for adding method.
    * @param    obj
    *           Reference to the instance of the object which will handle the
    *           requests. Should be <code>null</code> for static methods.
    * @param    name
    *           Name of the method in the target class
    * @param    signature
    *           List of types of parameters of the method in the target class.
    * @param    className
    *           Target class name.
    * @param    modToken
    *           The modification token retured from the original registration
    *           call (to this same method). May be <code>null</code> on the
    *           first call for this routing key (a new token will be created
    *           and returned in this case). If <code>non-null</code> on the
    *           first call, that object will be used as the modification token
    *           from then on.
    *
    * @return   The token used to authenticate the caller when a modification
    *           is requested.
    *
    * @throws   NoSuchMethodException
    *           is forwarded from <code>Class.getMethod</code> method.
    * @throws   SecurityException
    *           is forwarded from <code>Class.getMethod</code> or
    *           <code>addMethodImpl</code> methods.
    * @throws   ClassNotFoundException
    *           is forwarded from 
    *           <code>ClassLoader.getSystemClassLoader().loadClass</code> 
    *           method.
    */
   public static Object addMethod(RoutingKey key,
                                  Object     obj,
                                  String     name,
                                  Class[]    signature,
                                  String     className,
                                  Object     modToken)
   throws NoSuchMethodException,
          SecurityException,
          ClassNotFoundException
   {
      Class cls = MultiClassLoader.getClassLoader().loadClass(className);
      return addMethod(key, obj, name, signature, cls, modToken);
   }

   /**
    * Returns <code>RoutingKey</code> instance using provided method and
    * method group names. If group or method names are not present in the EIR,
    * then appropriate entries added to EIR.
    * 
    * @param    group
    *           Method group name.
    * @param    method
    *           Method name.
    * @return   Instance of RoutingKey which corresponds to method and method
    *           group names.
    */
   public static RoutingKey generateRoutingKey(String group,
                                               String method)
   {
      return dispatcher.getRoutingKey(group, method, true);
   }

   /**
    * Returns <code>RoutingKey</code> instance using provided method and
    * method group names. If group or method are not registered in the EIR
    * then <code>null</code> is returned.
    * 
    * @param    group
    *           Method group name.
    * @param    method
    *           Method name.
    * @return   Instance of RoutingKey which corresponds to method and method
    *           group names.
    */
   public static RoutingKey getRoutingKey(String group, String method)
   {
      return dispatcher.getRoutingKey(group, method, false);
   }

   /**
    * Start or re-start the dispatcher.
    * 
    * @param    num
    *           Number of threads in the thread pool.
    */
   static synchronized void initialize(int num)
   {
      if (dispatcher != null)
      {
         // stop all running dispatcher's threads from previous session
         dispatcher.closing = true;

         for (int i = 0; i < dispatcher.threads.length; i++)
         {
            dispatcher.threads[i].interrupt();
         }
      }
      
      dispatcher = new Dispatcher(num);
      sessMgr = SessionManager.get();
      Class<?> sessMgrClass = sessMgr.getClass();
      
      try
      {
         Method method = Dispatcher.class.getMethod("getRoutingKey",
                                                    String.class,
                                                    String.class);
         
         dispatcher.addMethodImpl(new RoutingKey(RoutingKey.GET_ROUTING_KEY),
                                  null,
                                  method,
                                  null);
         
         method = sessMgrClass.getMethod("shutdown");
         
         dispatcher.addMethodImpl(new RoutingKey(RoutingKey.SHUTDOWN_PACKAGE),
                                  sessMgr,
                                  method,
                                  null);
         
         method = Dispatcher.class.getMethod("interruptSession", 
                                             boolean.class, 
                                             int.class,
                                             int[].class);
         
         dispatcher.addMethodImpl(new RoutingKey(RoutingKey.INTERRUPT_SESSION),
                                  null,
                                  method,
                                  null);
         
         method = Dispatcher.class.getMethod("serverInterrupt");

         dispatcher.addMethodImpl(new RoutingKey(RoutingKey.SERVER_INTERRUPT),
                                  null,
                                  method,
                                  null);
         
         // export router node methods if necessary
         if (sessMgr.isRouter())
         {
            method = sessMgrClass.getDeclaredMethod("authenticateRemote",
                                                    String.class,
                                                    int.class,
                                                    Queue.class);

            dispatcher.addMethodImpl(new RoutingKey(RoutingKey.
                                                    AUTHENTICATE_REMOTE),
                                     sessMgr,
                                     method,
                                     null);

            method = sessMgrClass.getDeclaredMethod("terminateRemote",
                                                    int.class);

            dispatcher.addMethodImpl(new RoutingKey(RoutingKey.
                                                    TERMINATE_REMOTE),
                                     sessMgr,
                                     method,
                                     null);
         }
      }
      
      catch (Exception exc)
      {
         // Should not happen because exported entry points are local to net
         // package and therefore always exist and access to them is allowed,
         // unless a programming error exists.
         LOG.severe("", exc);
      }
   }

   /**
    * Services interrupt request from the remote end.
    *
    * @param    request
    *           The remote operation type. <code>true</code> for request.
    * @param    operationId
    *           The remote operation ID.
    * @param    pendingReplies
    *           The list of remote outstanding replies.
    *
    * @return   <code>true</code> if the interrupt request was successfully
    *           delivered.
    */
   public static boolean interruptSession(boolean request, 
                                          int     operationId, 
                                          int[]   pendingReplies)
   {
      Control ct = Control.locate(0);
      ct.setRemoteOperation(request, operationId, pendingReplies);

      try 
      {
         return Control.interrupt();
      }
      finally
      {
         ct.clearRemoteOperation();
      }
   }
   
   /**
    * Trigger an interrupt cause by a stop-after timeout, by inserting
    * a Ctrl-C into TypeAhead queue.
    */
   public static void serverInterrupt()
   {
      Control.triggerServerInterrupt();
   }

   /**
    * Checks whether this class is initialized.
    * 
    * @return   <code>true</code> if this class is running.
    */
   static synchronized boolean isRunning()
   {
      return dispatcher != null;
   }
   
   /**
    * Returns the singleton instance of this class.
    * 
    * @return   The singleton instance or <code>null</code> if this class
    *           has not yet been initialized.
    */
   static synchronized Dispatcher getInstance()
   {
      return dispatcher;
   }
   
   /**
    * Core worker for processing of inbound requests. Reads the request,
    * finds the specified method and invokes it if allowed by the security
    * settings.  For synchronous requests, any result or exception is sent 
    * back to the peer node.
    *
    * @param    request
    *           The inbound request to process. Cannot be <code>null</code>.
    * @param    set
    *           Override the current thread's security context based on the
    *           context associated with the queue.
    * @param    nr
    *           The network resource security plugin to check for access
    *           rights. Can be <code>null</code> on the client or if
    *           network entry point protection is disabled.
    */
   void processInbound(InboundRequest request,
                       boolean        set,
                       NetResource    nr)
   throws InterruptedException
   {
      // read the message
      Message       message = request.getMessage();
      Queue         queue   = request.getQueue();
      Control       ctrl    = null;
      RoutingKey    key     = message.getKey();
      MethodInvoker method  = null;
      
      synchronized (methodMap)
      {
         method = methodMap.get(key);
      }

      Throwable exception = null;
      Object    result    = null;
      boolean   intr      = false;

      if (method == null)
      {
         // invalid routing key
         exception = new NoSuchMethodException("No such exported method, " +
                                               key.getGroupID() + " / " +
                                               key.getMethodID());
      }
      else
      {
         // lock this method and track the request. only static exports can
         // be locked and tracked at this time.
         Method ifaceMthd = getMethod(key);
         Class<?> iface = getInterface(key);
         
         SessionManager sessMgr = SessionManager.get();
         
         boolean authRemote =
            (RoutingKey.AUTHENTICATE_REMOTE == key.getGroupID() &&
             RoutingKey.AUTHENTICATE_REMOTE == key.getMethodID());
         
         set = (set && !authRemote);
         
         if (set)
         {
            // set security context
            sessMgr.setContext(key, queue);
         }

         if (ifaceMthd != null && lockMethod(ifaceMthd))
         {
            // after the context has been set
            try
            {
               Session session = SessionManager.get().getSession();
               ExportTracker.trackRequest(iface, ifaceMthd, session);
            }
            finally
            {
               releaseMethod(ifaceMthd);
            }
         }
         
         // post-processing of the received message
         Throwable failedState = null;
         try
         {
            message = queue.getProtocol().applyChanges(message);
         }
         catch (Throwable t)
         {
            failedState = t;
            exception = new RuntimeException(failedState);
            if (LOG.isLoggable(Level.SEVERE) && !Utils.causeChainContains(t, criteria))
            {
               LOG.logp(Level.SEVERE,
                        "Dispatcher.processInbound()",
                        "",
                        "Unexpected throwable.",
                        t);
            }
         }

         boolean asyncRequest = message.isAsyncRequest();
         
         try
         {
            if (asyncRequest)
            {
               // if we are processing an async request, then mark this thread as async (to allow
               // it to send async requests)
               SessionManager.get().registerAsyncThread(Thread.currentThread());
            }
            
            // access control check (server only)
            if (nr != null)
            {
               String grp = getGroupName(key);
               String mth = getMethodName(key);
               
               if (!nr.checkExecuteAccess(grp, mth))
               {
                  throw new NoSuchMethodException("No such exported method " +
                                                  grp + ":" + mth);
               }
            }
             
            intr = (key.getGroupID() == RoutingKey.INTERRUPT_SESSION ||
                    key.getGroupID() == RoutingKey.SERVER_INTERRUPT);
            
            try
            {
               ctrl = queue.getControl(key.getContextID());
               
               // bypass interrupt processing state changes if we are
               // actually processing an asynchronous interrupt request
               if (!intr)
               {
                  // setup interrupt processing state
                  
                  // this may cause an un-acknowledged interrupt request on
                  // the local side to be honored, if so we must ensure that
                  // the finally block handles cleanup so this must be inside
                  // the try block
                  ctrl.pushUnconditionally(message.getRequestID());
               }
               else
               {
                  // obtain the global lock (this won't be released until
                  // after we send our reply so that this interrupt request
                  // will have its processing completed and the reply rec'd
                  // before the any further communication to the peer can
                  // occur)
                  ctrl.lock();
               }
            
               if (failedState == null)
               {
                  // do not allow the message to execute, if there were problems during sync state processing. 
               
                  Object[] payload = (Object[]) message.getPayload();
                  
                  // special handling for AUTHENTICATE_REMOTE payload;  method
                  // call requires that the *local* queue be passed in as the
                  // final parameter
                  if (authRemote)
                  {
                     int len = payload.length;
                     Object[] augmented = new Object[len + 1];
                     System.arraycopy(payload, 0, augmented, 0, len);
                     augmented[len] = queue;
                     payload = augmented;
                  }
                  
                  // execute the requested method
                  result = LOG.isLoggable(Level.FINER) ? trace(method, payload) : method.invoke(payload);
               }
            }
            
            finally
            {
               // clear interrupt processing state (if it was set
               // previously), this must be inside the main try to
               // ensure that any generated interruption will be
               // honored as the return value
               if (!intr)
               {
                  ctrl.popUnconditionally();
               }
            }
         }
         
         catch (Throwable t)
         {
            if (exception == null)
            {
               if (LOG.isLoggable(Level.SEVERE) && !Utils.causeChainContains(t, criteria))
               {
                  LOG.logp(Level.SEVERE,
                           "Dispatcher.processInbound()",
                           "",
                           "Unexpected throwable.",
                           t);
               }
               
               if (t instanceof Exception)
               {
                  exception = t;
               }
               else
               {
                  exception = new RuntimeException(t);
               }

               // try to extract original exception
               if (t instanceof InvocationTargetException)
               {
                  InvocationTargetException ite = (InvocationTargetException) t;
                     
                  Throwable cause = ite.getCause();

                  if (cause instanceof Exception)
                  {
                     exception = cause;
                  }
               }
            }
         }
         
         finally
         {
            if (asyncRequest)
            {
               // if we are processing an async request, deregister the dispatcher
               SessionManager.get().deregisterAsyncThread(Thread.currentThread());
            }

            if (set)
            {
               // drop security context
               sessMgr.restoreContext();
            }
         }
      }

      // it is possible that this thread is returning based on a
      // session abend/disconnection, in that case the session is dead
      // and we need to abort further processing (we certainly must not
      // try to forward any response back...)
      if (queue.isRunning())
      {
         int type = message.getType();

         if (exception != null)
         {
            message.setType(type == REQUEST_SYNCH ? REPLY_EXCEPTION_SYNC : REPLY_EXCEPTION_ASYNC);
            message.setPayload(exception);
         }
         else
         {
            message.setType(type == REQUEST_SYNCH ? REPLY_SYNC : REPLY_ASYNC);
            message.setPayload((Serializable) result);
         }

         queue.enqueueOutbound(message);
      }

      // even if the queue has been stopped while the request was being
      // processed, the control must unlock its core - as other threads may
      // still have pending operations which need to be finished
      if (intr)
      {
         // release the global lock, further communication to the peer
         // is OK now (on other threads)
         ctrl.unlock();
      }
   }
   
   /**
    * Main message processing method, core of the <code>Dispatcher</code>
    * class. It retrieves requests one by one from InboundQueue and processes
    * them: locates requested method in EIR, calls it and if necessary -
    * prepares reply message with result or exception.
    */
   void dispatch()
   {
      SessionManager sessMgr = SessionManager.get();
      sessMgr.setInitialContext();
      
      NetResource nr = (NetResource) sessMgr.getSecurityPlugin("net");
      
      Thread thread = Thread.currentThread();
      
      while (true)
      {
         try
         {
            // (re)establish the initial security context (this is only
            // needed here to allow the security cache to migrate each
            // thread to any newer initial context that may be set over
            // time
            sessMgr.setInitialContext();

            // clear traces of interrupted status, if any
            Thread.interrupted();

            // get the next request (this will block until a request is
            // available) and then process it
            processInbound(Queue.dequeueInbound(), true, nr);
         }
         
         catch (Throwable thr)
         {
            try
            {
               // deal with the server being shutdown
               if (thr instanceof InterruptedException)
               {
                  if (Queue.isInboundTerminated() || closing)
                  {
                     // no more dispatching to do because the server is stopping
                     break;
                  }
               }
               
               // other than shutdown, we ignore errors or exceptions that
               // occur during this loop because to exit would destabilize
               // the server
               
               // log that there was an unexpected failure
               if (LOG.isLoggable(Level.SEVERE))
               {
                  LOG.logp(Level.SEVERE,
                           "Dispatcher.dispatch()",
                           "",
                           "failure in dispatching loop",
                           thr);
               }
   
               if (thr instanceof OutOfMemoryError)
               {
                  // delay to try to recover from the OOME
                  try
                  {
                     Thread.sleep(5000);
                  }
                  catch (InterruptedException e)
                  {
                     // ignore
                  }
               }
            }
            
            catch (Throwable t)
            {
               // ignore any problems that may occur during error processing
               // and continue the loop
            }
         }
      }
   }

   /**
    * Lock all the given methods. This means no other deregistration or 
    * registration can be done on the given methods.
    * <p>
    * This is the same as calling 
    * {@link #lockMethods(Class[], Method[][], Object, boolean, boolean)
    * lockMethods(classList, methods, modToken, register, true)}.
    * <p>
    * Locking a method is possible if there is no other locking in effect and
    * there is no active session which has invoked the method.
    * 
    * @param   list
    *          The interfaces to export.  Each one will be exported using
    *          the fully qualified interface name as the group and each
    *          method will be exported by its name.
    * @param   methods
    *          The exported methods, grouped by defining interfaces.
    * @param   modToken
    *          When not null, it is the modification token from the original
    *          registration call.
    * @param   register
    *          flag indicating that this is method is called when registrating
    *          a new API, so semaphores need to be created.
    * 
    * @return  <code>true</code> if all methods were locked.
    */
   static boolean lockMethods(Class<?>[] list,
                              Method[][] methods, 
                              Object     modToken,
                              boolean    register)
   {
      return dispatcher.lockMethodsImpl(list, methods, 
                                        modToken, register, true);
   }

   /**
    * Lock all the given methods. This means no other deregistration or 
    * registration can be done on the given methods.
    * 
    * @param   list
    *          The interfaces to export.  Each one will be exported using
    *          the fully qualified interface name as the group and each
    *          method will be exported by its name.
    * @param   methods
    *          The exported methods, grouped by defining interfaces.
    * @param   modToken
    *          When not null, it is the modification token from the original
    *          registration call.
    * @param   register
    *          flag indicating that this is method is called when registrating
    *          a new API, so semaphores need to be created.
    * @param   checkActive
    *          flag indicating no lock is possible if there are active 
    *          sessions which have invoked this API.
    *          
    * @return  <code>true</code> if all methods were locked.
    */
   static boolean lockMethods(Class<?>[] list,
                              Method[][] methods, 
                              Object     modToken,
                              boolean    register,
                              boolean    checkActive)
   {
      return dispatcher.lockMethodsImpl(list, methods, 
                                        modToken, register, checkActive);
   }

   /**
    * Release all locks for the given methods.
    * 
    * @param   methods
    *          The exported methods, grouped by defining interfaces.
    */
   static void releaseMethods(Method[][] methods)
   {
      dispatcher.releaseMethodsImpl(methods);
   }

   /**
    * Remove the exported API represented by the given key. Only
    * static exported APIs should be removed.
    * 
    * @param   key
    *          The routing key for exported API.
    */
   static void removeMethod(RoutingKey key)
   {
      dispatcher.removeMethodImpl(key);
   }

   /**
    * Tracing worker that handles the local method call to the exported
    * interface and logs when the call is made (at FINER level) and logs
    * parameters, return values and an elapsed time in millis (at FINEST
    * level).
    *
    * @param    method
    *           Method to be invoked through reflection.
    * @param    args
    *           Arguments, if any, to <code>method</code>.
    *
    * @throws   Throwable
    *           if an exception/error occurs upon the invocation target,
    *           or if any problem occurs during pre- or post-processing.
    */
   private Object trace(MethodInvoker method, Object[] args)
   throws Throwable
   {
      return TraceHelper.trace(method, args, LOG, "Dispatcher.dispatch()");
   }

   /**
    * Add a method to the Exported Interface Registry (EIR). The instance of
    * <code>Method</code> is provided by the application. If a method with
    * the specified key is already present in the EIR it is only replaced if
    * the previously returned authentication token is given here, otherwise a
    * <code>SecurityException</code> is thrown.
    * <p>
    * This method is an "end-point" of all other addMethod(...) methods.
    * 
    * @param    key
    *           Routing data which is used as a key for adding the method.
    * @param    obj
    *           Reference to the instance of the object which will handle the
    *           requests. Should be <code>null</code> for static methods.
    * @param    method
    *           Instance of <code>java.lang.reflect.Method</code> which will
    *           handle requests.
    * @param    modToken
    *           The modification token retured from the original registration
    *           call (to this same method). May be <code>null</code> on the
    *           first call for this routing key (a new token will be created
    *           and returned in this case). If <code>non-null</code> on the
    *           first call, that object will be used as the modification token
    *           from then on.
    *
    * @return   The token used to authenticate the caller when a modification
    *           is requested.
    *
    * @throws   SecurityException
    *           If the caller is does not have the rights to register this
    *           method or group OR if the caller does not have the proper
    *           authentication token when trying to replace an existing method.
    */
   private synchronized Object addMethodImpl(RoutingKey key, 
                                             Object     obj,
                                             Method     method,
                                             Object     modToken)
   throws SecurityException
   {
      if (sessMgr.isRouter())
      {
         NetResource ar = (NetResource) sessMgr.getSecurityPlugin("net");
         
         if (ar != null)
         {
            String groupName  = getGroupName(key);
            String methodName = getMethodName(key);
            
            if (!ar.checkWriteAccess(groupName, methodName))
            {
               throw new SecurityException("Exporting " + groupName + ":" + 
                                           methodName + " is not allowed");
            }
         }
      }
      
      Set<RoutingKey> mappedKeySet = null;
      
      boolean allowMod = false;
      boolean newToken = true;
      
      if (modToken != null)
      {
         // find the routing key associated with the given modification token
         mappedKeySet = modTokens.get(modToken);
         
         if (mappedKeySet != null)
         {
            newToken = false;
            
            if (mappedKeySet.contains(key))
            {
               // the caller has the right mod token and the key matches, so
               // the modification is allowed
               allowMod = true;
            }
         }
      }

      synchronized (methodMap)
      {
         if (methodMap.get(key) != null && !allowMod)
         {
            // the key is already mapped and the caller is not allowed to modify
            throw new SecurityException(
                            "Replacing existing methods in EIR is not allowed");
         }
   
         methodMap.put(key, new MethodInvoker(obj, method));
      }

      if (newToken)
      {
         // create a unique object reference as the modification token (if
         // needed)
         if (modToken == null)
         {
            modToken = new Object();
         }
         
         // create the set of keys
         mappedKeySet = new HashSet<RoutingKey>();
         
         // save off the mapping of the token to the key that can be modified
         modTokens.put(modToken, mappedKeySet);
      }
      
      // add the routing key to the mapped list
      mappedKeySet.add(key);
      
      return modToken;
   }

   /**
    * Get <code>RoutingKey</code> instance using provided method and group
    * names. If group or method are not registered in the EIR and creation
    * is not allowed then <code>null</code> is returned. Otherwise the
    * appropriate entries are added into EIR and a valid key is generated.
    * <p>
    * This method implements proper access rights checks.
    * <p>
    * Must be synchronized as calls to this method can happen from any thread.
    * 
    * @param    group
    *           Group name.
    * @param    method
    *           Method name.
    * @param    canAdd
    *           Is creation of new instances is allowed or not.
    *           <code>true</code> if allowed.
    *
    * @return   Key which corresponds to method and group names.
    */
   private synchronized RoutingKey getRoutingKey(String  group,
                                                 String  method,
                                                 boolean canAdd)
   {
      // access rights check
      if (sessMgr.isRouter())
      {
         NetResource ar = (NetResource) sessMgr.getSecurityPlugin("net");
         
         if (ar != null)
         {
            boolean allowed = canAdd ? ar.checkWriteAccess(group, method)
                                     : ar.checkReadAccess(group, method);
            
            if (!allowed)
            {
               return null;
            }
         }
      }
      
      synchronized (keyMap)
      {
         KeyMapEntry grp = new KeyMapEntry(group);
         int grpPos = keyMap.indexOf(grp);
   
         if (grpPos < 0)
         {
            // no such group
            if (!canAdd)
            {
               return null;
            }
   
            // add method into group
            grp.getVector().add(method);
   
            grpPos = keyMap.size();
            keyMap.add(grp);
         }
         else
         {
            grp = keyMap.elementAt(grpPos); 
         }

         int mtdPos = grp.getVector().indexOf(method);
   
         if (mtdPos < 0)
         {
            // no such method
            if (!canAdd)
            {
               return null;
            }
   
            // add method into group
            mtdPos = grp.getVector().size();
            grp.getVector().add(method);
         }
   
         return new RoutingKey(grpPos, mtdPos);
      }
   }

   /**
    * Get the interface used to export the method with the given 
    * {@link RoutingKey key}, assuming that it is an interface.  If the 
    * {@link RoutingKey key} does not belong to an interface, then return 
    * <code>null</code>.
    * 
    * @param    key
    *           The {@link RoutingKey key} for which its exported method needs
    *           to be resolved.
    *           
    * @return   See above.
    */
   private Class<?> getInterface(RoutingKey key)
   {
      if (key.getGroupID() < 0)
      {
         // it's a system API
         return null;
      }

      String group = getGroupName(key);
      
      try
      {
         Class<?> iface = MultiClassLoader.getClassLoader().loadClass(group);
         if (!iface.isInterface())
         {
            return null;
         }
         
         return iface;
      }
      catch (ClassNotFoundException e)
      {
         if (LOG.isLoggable(Level.SEVERE))
         {
            LOG.severe("Could not find interface for group " + group);
         }

         // if interface can not be found, then null is returned
         return null;
      }
   }

   /**
    * Get the method exported with the given {@link RoutingKey key}, assuming
    * that it is defined by an interface.  If the {@link RoutingKey key} does
    * not belong to an interface or the method can not be found in the key's
    * interface hierarchy, then return <code>null</code>
    * 
    * @param    key
    *           The {@link RoutingKey key} for which its exported method needs
    *           to be resolved.
    *           
    * @return   See above.
    */
   private Method getMethod(RoutingKey key)
   {
      Method m = methodsByKey.get(key);
      if (m != null)
      {
         return m;
      }

      String name    = getMethodName(key);
      String group   = getGroupName(key);
      Class<?> iface = getInterface(key);
      
      if (iface == null)
      {
         return null;
      }
      
      // go through all methods and find the one with our name
      Method[] methods = iface.getMethods();
      for (int i = 0; i < methods.length; i++)
      {
         Method mthd = methods[i];
         
         if (mthd.toString().equals(name))
         {
            m = methodsByKey.putIfAbsent(key, mthd);
            return m != null ? m : mthd;
         }
      }
      
      // method not found in the interface (maybe something got out of sync)
      if (LOG.isLoggable(Level.WARNING))
      {
         LOG.warning("Could not find method " + name + " in group " + group);
      }
      return null;
   }
   
   /**
    * Gets the group name corresponding to the specified routing key.
    * Routing keys with negative gropiId are reported as having "system"
    * group.
    * @param  key
    *         routing key
    * @return group name where the key belongs
    */
   private String getGroupName(RoutingKey key)
   {
      if (key.getGroupID() < 0)
         return "system";

      synchronized (keyMap)
      {
         KeyMapEntry kme = keyMap.elementAt(key.getGroupID());
         return kme.getName();
      }
   }

   /**
    * Gets the method name corresponding to the specified routing key.
    * Routing keys with negative methodIds are reported as having special
    * builtin names within "system" group.
    * 
    * @param  key
    *         routing key
    * @return method name 
    */
   private String getMethodName(RoutingKey key)
   {
      int methodId = key.getMethodID();

      if (methodId < 0)
      {
         switch (methodId)
         {
            case RoutingKey.GET_ROUTING_KEY:
               return "route";

            case RoutingKey.AUTHENTICATE_REMOTE:
               return "authenticate";
            
            case RoutingKey.TERMINATE_REMOTE:
               return "terminate";

            case RoutingKey.SHUTDOWN_PACKAGE:
               return "shutdown";

            case RoutingKey.INTERRUPT_SESSION:
               return "interrupt";

            default:
               return "undefined";
         }
      }
      else
      {
         synchronized (keyMap)
         {
            KeyMapEntry kme = keyMap.elementAt(key.getGroupID());
            Vector<String> v = kme.getVector();
            return v.elementAt(methodId);
         }
      }
   }

   /**
    * Lock all the given methods. This means no other deregistration or 
    * registration can be done on the given methods.
    * 
    * @param   list
    *          The interfaces to export.  Each one will be exported using
    *          the fully qualified interface name as the group and each
    *          method will be exported by its name.
    * @param   methods
    *          The exported methods, grouped by defining interfaces.
    * @param   modToken
    *          When not null, it is the modification token from the original
    *          registration call.
    * @param   register
    *          flag indicating that this is method is called when registrating
    *          a new API, so semaphores need to be created.
    * @param   checkActive
    *          flag indicating no lock is possible if there are active 
    *          sessions which have invoked this API.
    *          
    * @return  <code>true</code> if all methods were locked.
    */
   private boolean lockMethodsImpl(Class<?>[] list,
                                   Method[][] methods, 
                                   Object     modToken,
                                   boolean    register,
                                   boolean    checkActive)
   {
      // TODO: make this to return "error codes" ?
      InterruptedException ie = null;
      boolean allLocked       = true;
      Set<Method> locked      = new HashSet<Method>();
      
      for (int i = 0; allLocked && i < methods.length; i++)
      {
         for (int j = 0; j < methods[i].length; j++)
         {
            // must lock this method only if is not used by active sessions
            if (checkActive && 
                ExportTracker.hasActiveSessions(list[i], methods[i][j]))
            {
               allLocked = false;
               break;
            }
   
            boolean allowMod = true;
            // TODO: what if is already registered ??
            if (modToken != null && !register)
            {
               allowMod = false;
               // find the routing key associated with the given modification 
               // token
               Set<RoutingKey> mappedKeySet = null;
               synchronized (this)
               {
                  mappedKeySet = modTokens.get(modToken);
               }
   
               Class<?> clazz = methods[i][j].getDeclaringClass();
               
               String group = RemoteObject.calcGroupName(clazz);
               String method = RemoteObject.calcKeyName(methods[i][j]);
               
               RoutingKey key = getRoutingKey(group, method, false);
               
               if (key != null && 
                   mappedKeySet != null && 
                   mappedKeySet.contains(key))
               {
                  // the caller has the right mod token and the key matches,
                  // so the modification is allowed
                  allowMod = true;
               }
            }
   
            if (!allowMod)
            {
               allLocked = false;
               break;
            }
            
            Semaphore sem = null;
            synchronized (staticMethodLocks)
            {
               sem = staticMethodLocks.get(methods[i][j]);
   
               if (register)
               {
                  if (sem == null)
                  {
                     staticMethodLocks.put(methods[i][j], 
                                           sem = new Semaphore(1));
                  }
                  else
                  {
                     // this must be a replace, so leave the semaphore intact
                  }
               }
            }
   
            try
            {
               sem.acquire();
            }
            catch (InterruptedException ex)
            {
               allLocked = false;
               ie = ex;
               break;
            }
            
            locked.add(methods[i][j]);
         }
      }
      
      if (!allLocked)
      {
         // if couldn't lock all of them, then release all those locked
         for (Method m : locked)
         {
            releaseMethod(m);
         }
      }
      
      if (ie != null)
      {
         throw new RuntimeException(ie);
      }
      
      return allLocked;
   }

   /**
    * Release all locks for the given methods.
    * 
    * @param   methods
    *          The exported methods, grouped by defining interfaces.
    */
   private void releaseMethodsImpl(Method[][] methods)
   {
      for (int i = 0; i < methods.length; i++)
      {
         for (int j = 0; j < methods[i].length; j++)
         {
            releaseMethod(methods[i][j]);
         }
      }
   }

   /**
    * Release the lock for the given method.
    * 
    * @param   method
    *          The exported method.
    */
   private void releaseMethod(Method method)
   {
      Semaphore sem = null;
   
      synchronized (staticMethodLocks)
      {
         sem = staticMethodLocks.get(method);
      }
   
      if (sem != null)
      {
         sem.release();
      }
   }

   /**
    * Locks the given method, without checking if there are active sessions.
    * 
    * @param   method
    *          The exported method.
    * 
    * @throws  InterruptedException
    *          If thread is interrupted during semaphore acquire.
    */
   private boolean lockMethod(Method method)
   throws InterruptedException
   {
      boolean locked = false;
      Semaphore  sem = null;
      
      // this does not and must not check for active sessions which have
      // invoked this exported method. this is needed only to ensure there is 
      // an exclusive lock on this method.
      
      synchronized (staticMethodLocks)
      {
         sem = staticMethodLocks.get(method);
      }
   
      if (sem == null)
      {
         return false;
      }
      
      try
      {
         sem.acquire();
         locked = true;
      }
      catch (InterruptedException e)
      {
         // thread was interrupted, nothing else to do
         throw e;
      }
      
      return locked;
   }

   /**
    * Remove the exported API represented by the given key. Only
    * static exported APIs should be removed.
    * 
    * @param   key
    *          The routing key for exported API.
    */
   private synchronized void removeMethodImpl(RoutingKey key)
   {
      // if I got here, all security checks are in place.
      // only static methods can be removed.

      Method method = getMethod(key);
      if (method == null)
      {
         return;
      }
      
      synchronized (methodMap)
      {
         methodMap.remove(key);
      }
   
      // remove this routing key from all modToken(s)
      Iterator<Set<RoutingKey>> iter = modTokens.values().iterator();
      while (iter.hasNext())
      {
         Set<RoutingKey> set = iter.next();
         set.remove(key);
   
         if (set.isEmpty())
         {
            iter.remove();
         }
      }
      
      // remove the semaphore for this method, but I need the interface method
      // and not the real method (the one in methodinvoker)
      synchronized (staticMethodLocks)
      {
         staticMethodLocks.remove(method);
      }
      
      int methodId = key.getMethodID();
      int groupId = key.getGroupID();
      synchronized (keyMap)
      {
         KeyMapEntry kme = keyMap.elementAt(groupId);
         Vector<String> v = kme.getVector();
         v.set(methodId, null);
         
         // remove all trailing nulls
         while (v.size() > 0 && v.elementAt(v.size() - 1) == null)
         {
            v.remove(v.size() - 1);
         }
      
         if (v.isEmpty())
         {
            keyMap.set(groupId, null);
         }
      
         // remove all trailing nulls
         while (keyMap.size() > 0 && keyMap.elementAt(keyMap.size() - 1) == null)
         {
            keyMap.remove(keyMap.size() - 1);
         }
      }
   }

   /**
    * This class is used to start threads running <code>dispatch()</code>
    * method.
    */
   private class DispatcherStub
   implements Runnable
   {
      /**
       * Implementation of <code>Runnable</code> interface. It simply calls
       * <code>Dispatcher.dispatch()</code>.
       */
      public void run()
      {
         dispatch();
      }
   }

   /**
    * This class is used as an entry in the Key Map. It corresponds to named
    * group of methods and contains names of the methods which belong to
    * particular group.
    */
   private static class KeyMapEntry
   {
      /** name of the entry (corresponds to group name) */
      private final String name;

      /** Vector of method names associated with this group */
      private final Vector<String> vector = new Vector<String>();

      /**
       * Constructs a <code>KeyMapEntry</code> using given group name.
       * 
       * @param    group
       *           Group name.
       */
      KeyMapEntry(String group)
      {
         name = group;
      }

      /**
       * This method is used by <code>Vector.indexOf()</code> method.
       * 
       * @param    obj
       *           An object to compare with.
       *
       * @return   <code>true</code> if object, passed as a parameter, is
       *           equal to this instance and <code>false</code> otherwise.
       */
      public boolean equals(Object obj)
      {
         if (this == obj)
            return true;

         if (obj instanceof KeyMapEntry)
            return name.equals(((KeyMapEntry) obj).getName());

         return false;
      }

      /**
       * Provides access to group name.
       * 
       * @return   The group name.
       */
      String getName()
      {
         return name;
      }

      /**
       * Provides access to the vector of method names.
       * 
       * @return   The vector of names associated with this group.
       */
      Vector<String> getVector()
      {
         return vector;
      }
   }   
}