RemoteObject.java

/*
** Module   : RemoteObject.java
** Abstract : provides a simple mechanism to implement a remote interface
**
** Copyright (c) 2006-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ------------------Description-------------------
** 001 GES 20060209   @24506 First version which can handle the export,
**                           import and transaction processing for a 
**                           given list of interfaces. This handles all
**                           of the requirements for creating a fully
**                           functional remote interface, just by
**                           defining and implementing a Java interface
**                           as if it was a local object.  No net package
**                           processing is needed.
** 002 GES 20060211   @24544 Added support for a configurable timeout,
**                           and now defaults the timeout to 0.
** 003 NVS 20060222   @24623 Fixed bug in findMatchingStaticMethod so now
**                           static methods are properly exported.
** 004 NVS 20060614   @27195 Implemented deferred registrations. Requests
**                           to register interfaces are deferred if by the
**                           time of call the Dispatcher is not running.
**                           A new method registerDeferred() then can be
**                           used to perform all deferred registrations
**                           at once in the order they were received.
** 005 GES 20060717   @28152 Added logging of all method calls if level
**                           FINER is specified. Added parameter and
**                           return value logging if FINEST is specified.
** 006 GES 20061219   @31721 Removed deferred registration processing
**                           since this is no longer needed. Added some
**                           safety code to the invocation handler to
**                           map calls to methods defined in Object to
**                           the local instance since these are not remote
**                           APIs.  For example, this allows such proxy
**                           objects to be stored in hash maps.
** 007 GES 20070110   @31793 Fix to eliminate infinite recursion when
**                           calling hashCode, toString or equals methods
**                           on a remote proxy object.
** 008 GES 20070115   @31836 Match minor interface change in base class.
** 009 NVS 20070312   @32380 Added some missing logging of the exported
**                           methods.
** 010 GES 20070406   @32849 Reworked H009 to remove security manager
**                           dependency (which was inappropriate) and
**                           to improve performance of the logging code
**                           in the case where no logging will actually
**                           occur.
** 011 ECF 20071102   @35886 Refactored net package. Replaced Queue with
**                           Session. Use SessionManager instead of
**                           SecurityManager.
** 012 CA  20090824   @43719 Errors caused by InterruptedException should
**                           be handled separately, and be packed in a
**                           generic RuntimeException. This fixes CTRL+C 
**                           pressed while retrieving the routing key for
**                           an export.
** 013 SIY 20090831   @43796 Carefully ignore IllegalStateException.
** 014 CA  20090917   @43929 Fix abnormal connection end. If the routing key
**                           can't be obtained or an IllegalStateException is
**                           encountered, a SilentUnwindException will be 
**                           thrown, as the communication with the client 
**                           wasn't possible.
** 015 GES 20111012          Moved common invocation handler logic to a common
**                           base class. Shifted HLO usage to a contained
**                           member.
** 016 GES 20111018          Added local redirection support.
** 017 GES 20111111          Separated local and remote instance server
**                           registration. The two use cases are never used
**                           simultaneously (at least right now), so there is
**                           no reason to do both in one call. It makes the
**                           caller a bit more complex, but with the advantage
**                           of having less overhead/memory usage.
** 018 CA  20111209          Added possibility to deregister a static network
**                           server.
** 019 CA  20131218          Added logging for cases when a method backed by a static proxy can't
**                           be resolved.
** 020 CA  20140513          Added a weight for the context-local var, to ensure predetermined 
**                           order during context reset.
** 021 ECF 20150715          Replace StringBuffer with StringBuilder.
** 022 VVT 20200203          registerStaticNetworkServer(): fixed error handling logic;
**                           Missing @Override annotation added.
**                           findMatchingStaticMethod(): code cleanup
** 023 CA  20220515          The active session is now set for leaf sessions, too - this is required for a 
**                           change in RemoteObject.obtainInstance, where first a check is done for an existing
**                           local proxy, and after that a network instance is obtained (a requirement for the
**                           server-side OS resources support, like memptr).
** 024 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
** 025 GBB 20230517          Redundant isLoggable check removed.
** 026 GBB 20230725          RemoteAccess tracing flag replaced by direct call to LOG.isLoggable.
** 027 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.lang.reflect.*;
import java.util.*;
import java.util.logging.*;
import com.goldencode.p2j.classloader.MultiClassLoader;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;

/**
 * Helper class which can handle the export, import and transaction
 * processing for a given list of interfaces. This handles all of the
 * requirements for creating a fully functional remote interface, just by
 * defining and implementing a Java interface as if it was a local object.
 * <p>
 * No net package processing is needed in the implementation class or in
 * the interface.  This class is designed for convenience.  Usage avoids the
 * tedious, hard-coded requirements of exporting and importing network 
 * interfaces.  To use it:
 * <ol>
 *    <li> Define an interface which provides the service or API required.
 *    <li> Create one or more implementation classes that implement the
 *         interface, according to your application needs.
 *    <li> On the network node which provides (exports) the API, use one of
 *         the following mechanisms:
 *    <ul>
 *       <li> Instantiate an instance of every object you wish to export.
 *            For each object, call the {@link #registerNetworkServer} method 
 *            passing the list of interfaces to be exported and the instance
 *            that provides the implementation.  This exports your interface.
 *            Calls will now be dispatched to this object with no further
 *            work.
 *       <li> Call the {@link #registerStaticNetworkServer} method with the
 *            list of interfaces to be exported and the class object that
 *            provides the <code>static</code> implementation.  This exports
 *            your interface.  Calls will now be dispatched to the
 *            <code>static</code> methods of that class with no further work.
 *    </ul>
 *    <li> On the network node that needs to remotely access the interface,
 *         call the {@link #obtainNetworkInstance} method passing the
 *         interface that is being requested. This assumes that a connection
 *         to the remote node already exists.  The object returned will
 *         provide transparent access to the remote API with no further 
 *         effort.
 *    <li> It is possible to use local redirection. This is non-network usage
 *         where both sides of the invocation chain are in the same JVM. This
 *         allows systems which were designed to be split over the network to
 *         also be used within a single JVM without any networking in between.
 *         The registration is the same as above (for the exports).  But to
 *         obtain an instance of the requested interface, the user calls the
 *         {@link #obtainLocalInstance} method. No network connection or
 *         session is needed.
 * </ol>
 */
public class RemoteObject
{
   /** Logger (safe as a JVM-wide value rather than as context-local). */
   private static final CentralLogger LOG =
      CentralLogger.get(RemoteObject.class.getName());
      
   /** Stores context-local state variables. */
   private static final ContextLocal<WorkArea> work = 
   new ContextLocal<WorkArea>()
   {
      @Override
      public WeightFactor getWeight()
      {
         return WeightFactor.LAST;
      };

      @Override
      protected synchronized WorkArea initialValue()
      {
         return new WorkArea();
      }
   };
      
   /** Stores static server method mappings (global for the server JVM). */
   private static final Map<Class<?>, Map<Method, Method>> localStaticServers = new HashMap<>();
   
   /** Stores static server mappings (global for the server JVM). */
   private static final Map<Class<?>, Class<?>> localImplStaticServers = new HashMap<>();
   
   /**
    * Exports all methods of the given interface from the local node (to
    * other nodes). All remote calls to these exported methods will be
    * subsequently dispatched to the given implementation instance which
    * is required to implement the exported interface.
    *
    * @param    iface
    *           The interface to export.  It will be exported using
    *           the fully qualified interface name as the group and each
    *           method will be exported by its name.
    * @param    impl
    *           The instance of an object that implements the exported 
    *           interface.  All method calls to the exported methods will be
    *           redirected to the given instance.
    *
    * @return   The token used to authenticate the caller when a subsequent
    *           modification is requested.
    */
   public static Object registerNetworkServer(Class<?> iface, Object impl)
   {
      return registerNetworkServer(new Class[] { iface }, impl, null);
   }
   
   /**
    * Exports all methods of the given interface from the local node (to
    * other nodes). All remote calls to these exported methods will be
    * subsequently dispatched to the given implementation instance which
    * is required to implement the exported interface.
    *
    * @param    iface
    *           The interface to export.  It will be exported using
    *           the fully qualified interface name as the group and each
    *           method will be exported by its name.
    * @param    impl
    *           The instance of an object that implements the exported 
    *           interface.  All method calls to the exported methods will be
    *           redirected to the given instance.
    * @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 interface (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 subsequent
    *           modification is requested.
    */
   public static Object registerNetworkServer(Class<?> iface,
                                              Object   impl,
                                              Object   modToken)
   {
      return registerNetworkServer(new Class[] { iface }, impl, modToken);
   }
   
   /**
    * Exports all methods in the given list of interfaces from the local node
    * (to other nodes).  All remote calls to these exported methods will be
    * subsequently dispatched to the given implementation instance which
    * is required to implement all of the exported interfaces.
    *
    * @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    impl
    *           The instance of an object that implements all of the 
    *           exported interfaces.  All method calls to the exported
    *           methods will be redirected to the given instance.
    *
    * @return   The token used to authenticate the caller when a subsequent
    *           modification is requested.
    */
   public static Object registerNetworkServer(Class<?>[] list, Object impl)
   {
      return registerNetworkServer(list, impl, null);
   }
   
   /**
    * Exports all methods in the given list of interfaces from the local node
    * (to other nodes).  All remote calls to these exported methods will be
    * subsequently dispatched to the given implementation instance which
    * is required to implement all of the exported interfaces.
    *
    * @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    impl
    *           The instance of an object that implements all of the 
    *           exported interfaces.  All method calls to the exported
    *           methods will be redirected to the given instance.
    * @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 interface (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 subsequent
    *           modification is requested.
    */
   public static Object registerNetworkServer(Class<?>[] list,
                                              Object     impl,
                                              Object     modToken)
   {
      // check to see if the Dispatcher is up
      if (!Dispatcher.isRunning())
      {
         throw new IllegalStateException("Must only be called AFTER the " +
                                         "Dispatcher is initialized.");
      }
      
      if (modToken == null)
      {
         // create and use the same instance for all registrations associated
         // with this same method call
         modToken = new Object();
      }
      
      // loop through interface list
      for (int i = 0; i < list.length; i++)
      {
         String   group   = calcGroupName(list[i]);
         Method[] methods = calcExportedMethods(list[i]);
         
         for (int j = 0; j < methods.length; j++)
         {
            String name = calcKeyName(methods[j]);
            
            // generate a routing key
            RoutingKey key = Dispatcher.generateRoutingKey(group, name);
            
            if (key == null)
            {
               throw new RuntimeException("Unable to get routing key for " +
                                          group + ":" + name);               
            }
            
            // add the method to the dispatcher
            Dispatcher.addMethod(key, impl, methods[j], modToken);            
            logExport(group, name);
         }
      }
      
      return modToken;
   }
   
   /**
    * Register all methods in the given interface from the local node as a
    * local server.  These interfaces are only available in-process, they
    * will not be accessible via remote access.
    *
    * @param    iface
    *           The interface to register.
    * @param    impl
    *           The instance of an object that implements all of the 
    *           exported interfaces.  All method calls to the registered
    *           methods will be redirected to the given instance.
    * @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 interface (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 subsequent
    *           modification is requested.
    */
   public static Object registerLocalServer(Class<?> iface,
                                            Object   impl,
                                            Object   modToken)
   {
      return registerLocalServer(new Class[] { iface }, impl, modToken);
   }
   
   /**
    * Register all methods in the given list of interfaces from the local node
    * as a local server.  These interfaces are only available in-process, they
    * will not be accessible via remote access.
    *
    * @param    list
    *           The interfaces to register.
    * @param    impl
    *           The instance of an object that implements all of the 
    *           exported interfaces.  All method calls to the registered
    *           methods will be redirected to the given instance.
    * @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 interface (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 subsequent
    *           modification is requested.
    */
   public static Object registerLocalServer(Class<?>[] list,
                                            Object     impl,
                                            Object     modToken)
   {
      if (modToken == null)
         modToken = new Object();
      
      // loop through interface list
      for (int i = 0; i < list.length; i++)
      {
         Method[] methods = calcExportedMethods(list[i]);
         
         WorkArea wa = work.get();
         
         // save off context-local state to allow local redirection
         synchronized (wa)
         {
            Object authToken = wa.modTokens.get(list[i]);
            
            if (authToken == null || authToken == modToken)
            {
               Set<Method> set = new HashSet<Method>(Arrays.asList(methods));
               Set<Method> ro  = Collections.unmodifiableSet(set);
               wa.localInstanceMethods.put(list[i], ro);
               wa.localInstanceServers.put(list[i], impl);
            }
         }
      }
      
      return modToken;
   }
   
   /**
    * Register all methods in the given interface from the local node as EITHER:
    * <p>
    * <ol>
    *    <li> If <code>single</code> is <code>true</code> then the methods
    *         will be exported for remote access using 
    *         {@link #registerNetworkServer}.
    *    <li> If <code>single</code> is <code>false</code> then the methods
    *         will be exported for local access using 
    *         {@link #registerLocalServer}.
    * </ol>
    *
    * @param    iface
    *           The interface to register.
    * @param    impl
    *           The instance of an object that implements all of the 
    *           exported interfaces.  All method calls to the registered
    *           methods will be redirected to the given instance.
    * @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 interface (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.
    * @param    local
    *           <code>true</code> to register purely on a local basis (no
    *           remote/network access to these methods).
    *
    * @return   The token used to authenticate the caller when a subsequent
    *           modification is requested.
    */
   public static Object registerServer(Class<?> iface,
                                       Object   impl,
                                       Object   modToken,
                                       boolean  local)
   {
      return registerServer(new Class[] { iface }, impl, modToken, local);
   }
   
   /**
    * Register all methods in the given interface from the local node as EITHER:
    * <p>
    * <ol>
    *    <li> If <code>single</code> is <code>true</code> then the methods
    *         will be exported for remote access using 
    *         {@link #registerNetworkServer}.
    *    <li> If <code>single</code> is <code>false</code> then the methods
    *         will be exported for local access using 
    *         {@link #registerLocalServer}.
    * </ol>
    *
    * @param    list
    *           The interfaces to register.
    * @param    impl
    *           The instance of an object that implements all of the 
    *           exported interfaces.  All method calls to the registered
    *           methods will be redirected to the given instance.
    * @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 interface (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.
    * @param    local
    *           <code>true</code> to register purely on a local basis (no
    *           remote/network access to these methods).
    *
    * @return   The token used to authenticate the caller when a subsequent
    *           modification is requested.
    */
   public static Object registerServer(Class<?>[] list,
                                       Object     impl,
                                       Object     modToken,
                                       boolean    local)
   {
      if (local)
      {
         return registerLocalServer(list, impl, modToken);
      }
      return registerNetworkServer(list, impl, modToken);
   }
   
   /**
    * Exports all <code>static</code> methods in the given backing class that 
    * exactly match the method signatures in the given list of interfaces
    * from the local node (to other nodes).  All remote calls to these
    * exported methods will be subsequently dispatched to the given
    * implementation class.
    * <p>
    * The nature of Java interfaces precludes a <code>static</code>
    * implementation.  This means that the backing class can't use the
    * <code>implements</code> keyword even though it must have an exact
    * match of the exported methods actually implemented.
    *
    * @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    backing
    *           The class that implements all of the exported interfaces as
    *           <code>static</code> methods.  All method calls to the
    *           exported methods will be redirected to that class on a
    *           <code>static</code> basis.
    *
    * @return   The token used to authenticate the caller when a subsequent
    *           modification is requested.
    */
   public static Object registerStaticNetworkServer(Class<?>[] list, 
                                                    Class<?> backing)
   {
      return registerStaticNetworkServer(list, backing, null);
   }
   
   /**
    * Get the implementation class for the given interface, exported as a
    * static server.  If the interface is not exported, then it returns 
    * <code>null</code>
    * 
    * @param   ifaceName
    *          The interface name.
    *          
    * @return  See above.
    */
   public static Class<?> getImplClass(String ifaceName)
   {
      Class<?> iface = null;
      Class<?> impl  = null;
      try
      {
         iface = MultiClassLoader.getClassLoader().loadClass(ifaceName);
      }
      catch (@SuppressWarnings("unused") ClassNotFoundException e)
      {
         // interface not found
         return null;
      }
      
      synchronized (localStaticServers)
      {
         impl = localImplStaticServers.get(iface);
      }
      
      return impl;
   }

   /**
    * Lock all static exports which are specified in the given list. Note that
    * the list can contain an interface which is not exported.
    * 
    * @param   list
    *          The interface name list.
    *          
    * @return  <code>true</code> if all static exports in this list were 
    *          locked.
    */
   public static boolean deregisterStaticNetworkServer(String[] list)
   {
      List<Class<?>> exported = new ArrayList<Class<?>>();
      
      synchronized (localStaticServers)
      {
         // get all exported interfaces which are contained in the list
         for (int i = 0; i < list.length; i++)
         {
            Class<?> iface = null;
            try
            {
               iface = MultiClassLoader.getClassLoader().loadClass(list[i]);
            }
            catch (@SuppressWarnings("unused") ClassNotFoundException e)
            {
               // ignore, can not happen
            }
         
            if (localStaticServers.containsKey(iface))
            {
               exported.add(iface);
            }
         }
      }
      
      Class<?>[] ifcs = exported.toArray(new Class<?>[0]);

      return deregisterStaticNetworkServer(ifcs);
   }
   
   /**
    * Exports all <code>static</code> methods in the given backing class that 
    * exactly match the method signatures in the given list of interfaces
    * from the local node (to other nodes).  All remote calls to these
    * exported methods will be subsequently dispatched to the given
    * implementation class.
    * <p>
    * The nature of Java interfaces precludes a <code>static</code>
    * implementation.  This means that the backing class can't use the
    * <code>implements</code> keyword even though it must have an exact
    * match of the exported methods actually implemented.
    *
    * @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    backing
    *           The class that implements all of the exported interfaces as
    *           <code>static</code> methods.  All method calls to the
    *           exported methods will be redirected to that class on a
    *           <code>static</code> basis.
    * @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 interface (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 subsequent
    *           modification is requested.
    */
   public static Object registerStaticNetworkServer(Class<?>[] list, 
                                                    Class<?> backing,
                                                    Object   modToken)
   {
      // check to see if the Dispatcher is up
      if (!Dispatcher.isRunning())
      {
         throw new IllegalStateException("Must only be called AFTER the " +
                                         "Dispatcher is initialized.");
      }

      if (modToken == null)
      {
         // create and use the same instance for all registrations associated
         // with this same method call
         modToken = new Object();
      }

      Method[][] methods = calcExportedMethods(list);

      // go through and lock all exports first
      if (!Dispatcher.lockMethods(list, methods, modToken, true))
      {
         return false;
      }
      
      // loop through interface list
      try
      {
         for (int i = 0; i < list.length; i++)
         {
            String group = calcGroupName(list[i]);

            HashMap<Method, Method> map = new HashMap<Method, Method>();
            
            for (int j = 0; j < methods[i].length; j++)
            {
               String name = calcKeyName(methods[i][j]);
               Method real = findMatchingStaticMethod(backing, methods[i][j]);
               
               if (real == null && LOG.isLoggable(Level.SEVERE))
               {
                  String msg = "Unable to find matching static method %s in backing class %s.";
                  LOG.logp(Level.SEVERE, "RemoteObject", "registerStaticNetworkServer", 
                           String.format(msg, name, backing.getName()));
                  continue;
               }
               
               // generate a routing key
               RoutingKey key = Dispatcher.generateRoutingKey(group, name);
               
               if (key == null)
               {
                  throw new RuntimeException("Unable to get routing key for " +
                                             group + ":" + name);               
               }
               
               // create method mappings for local redirection
               map.put(methods[i][j], real);
               
               // add the static method to the dispatcher
               Dispatcher.addMethod(key, null, real, modToken);            
               logExport(group, name);
            }
            
            // save off method mapping state
            synchronized (localStaticServers)
            {
               localStaticServers.put(list[i], Collections.unmodifiableMap(map));
               localImplStaticServers.put(list[i], backing);
            }
         }
      }
      finally
      {
         Dispatcher.releaseMethods(methods);
      }
      
      return modToken;
   }

   
   /**
    * Remove all exported <code>static</code> methods in the given list of 
    * interfaces.
    * <p>
    * If there are active sessions which have used API(s) in these exported
    * interface, the deregistration will not be performed for any of the 
    * exported interface.
    * <p>
    * The deregistration bypasses the <code>modToken</code> check, as this
    * will be done by other clients (i.e. from the admin applet).
    * 
    * @param    list
    *           The exported interfaces to be removed.
    *
    * @return   <code>true</code> if deregistration was completed fully.
    */
   static boolean deregisterStaticNetworkServer(Class<?>[] list)
   {
      // check to see if the Dispatcher is up
      if (!Dispatcher.isRunning())
      {
         throw new IllegalStateException("Must only be called AFTER the " +
                                         "Dispatcher is initialized.");
      }

      Method[][] methods = calcExportedMethods(list);

      // go through and lock all exports first
      if (!Dispatcher.lockMethods(list, methods, null, false))
      {
         return false;
      }
      
      try
      {
         for (int i = 0; i < list.length; i++)
         {
            HashMap<Method, Method> map = null;
            String   group   = calcGroupName(list[i]);
            
            // obtain a copy of the exports
            synchronized (localStaticServers)
            {
               map = new HashMap<Method, Method>();
               map.putAll(localStaticServers.get(list[i]));
            }
   
            for (int j = 0; j < methods[i].length; j++)
            {
               String name = calcKeyName(methods[i][j]);
               
               // get the routing key
               RoutingKey key = Dispatcher.getRoutingKey(group, name);
               
               if (key == null)
               {
                  throw new RuntimeException("Unable to get routing key for " +
                                             group + ":" + name);               
               }
               
               // at this point, this method is locked so no other clients can
               // invoke this method and also there are no active sessions;
               // it is safe to remove it.
               Dispatcher.removeMethod(key);
               map.remove(methods[i][j]);
            }
   
            // save off method mapping state
            synchronized (localStaticServers)
            {
               if (map.isEmpty())
               {
                  localStaticServers.remove(list[i]);
                  localImplStaticServers.remove(list[i]);
               }
               else
               {
                  localStaticServers.put(list[i], 
                                         Collections.unmodifiableMap(map));
               }
            }
         }
      } 
      finally
      {
         Dispatcher.releaseMethods(methods);
      }
      
      return true;
   }
   
   /**
    * Creates a unique instance of an object that implements the given 
    * interface and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the current 
    * session's network connection (message queue).
    * <p>
    * Synchronous transactions will have a timeout set to 0 (indefinite
    * wait).
    * 
    * @param    iface
    *           The interface for which a network instance must be created. 
    *
    * @return   A network aware object that implements the given interface.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?> iface)
   {
      return obtainNetworkInstance(new Class[] { iface }, 0);
   }
   
   /**
    * Creates a unique instance of an object that implements the given 
    * interface and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the current 
    * session's network connection (message queue).
    * 
    * @param    iface
    *           The interface for which a network instance must be created. 
    * @param    timeout
    *           Timeout in milliseconds for synchronous transactions to
    *           wait before automatically unblocking.  Use 0 to set an
    *           indefinite wait.
    *
    * @return   A network aware object that implements the given interface.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?> iface, int timeout)
   {
      return obtainNetworkInstance(new Class[] { iface }, timeout);
   }
   
   /**
    * Creates a unique instance of an object that implements the given 
    * interface and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the specified 
    * network connection (message queue).
    * <p>
    * Synchronous transactions will have a timeout set to 0 (indefinite
    * wait).
    * 
    * @param    iface
    *           The interface for which a network instance must be created. 
    * @param    session
    *           The network connection which the returned object should
    *           represent.  If <code>null</code> the current session will be
    *           used.
    *
    * @return   A network aware object that implements the given interface.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?> iface, Session session)
   {
      return obtainNetworkInstance(new Class[] { iface }, session, 0);
   }
   
   /**
    * Creates a unique instance of an object that implements the given 
    * interface and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the specified 
    * network connection (message queue).
    * 
    * @param    iface
    *           The interface for which a network instance must be created. 
    * @param    session
    *           The network connection which the returned object should
    *           represent.  If <code>null</code> the current session will be
    *           used.
    * @param    timeout
    *           Timeout in milliseconds for synchronous transactions to
    *           wait before automatically unblocking.  Use 0 to set an
    *           indefinite wait.
    *
    * @return   A network aware object that implements the given interface.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?> iface,
                                              Session session,
                                              int timeout)
   {
      return obtainNetworkInstance(new Class[] { iface }, session, timeout);
   }
      
   /**
    * Creates a unique instance of an object that implements the given 
    * interfaces and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the current 
    * session's network connection (message queue).
    * <p>
    * Synchronous transactions will have a timeout set to 0 (indefinite
    * wait).
    * 
    * @param    list
    *           The interfaces for which a network instance must be created. 
    *
    * @return   A network aware object that implements the given interfaces.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?>[] list)
   {
      return obtainNetworkInstance(list, null, 0);
   }
   
   /**
    * Creates a unique instance of an object that implements the given 
    * interfaces and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the current 
    * session's network connection (message queue).
    * 
    * @param    list
    *           The interfaces for which a network instance must be created. 
    * @param    timeout
    *           Timeout in milliseconds for synchronous transactions to
    *           wait before automatically unblocking.  Use 0 to set an
    *           indefinite wait.
    *
    * @return   A network aware object that implements the given interfaces.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?>[] list, int timeout)
   {
      return obtainNetworkInstance(list, null, timeout);
   }
   
   /**
    * Creates a unique instance of an object that implements the given 
    * interfaces and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the specified 
    * network connection (message queue).
    * <p>
    * Synchronous transactions will have a timeout set to 0 (indefinite
    * wait).
    * 
    * @param    list
    *           The interfaces for which a network instance must be created. 
    * @param    session
    *           The network connection which the returned object should
    *           represent.  If <code>null</code> the current session will be
    *           used.
    *
    * @return   A network aware object that implements the given interfaces.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?>[] list, Session session)
   {
      return obtainNetworkInstance(list, session, 0);
   }
      
   /**
    * Creates a unique instance of an object that implements the given 
    * interfaces and upon which any method calls will be transparently 
    * executed by the exported methods on the other side of the specified 
    * network connection (message queue).
    * 
    * @param    list
    *           The interfaces for which a network instance must be created. 
    * @param    session
    *           The network connection which the returned object should
    *           represent.  If <code>null</code> the current session will be
    *           used.
    * @param    timeout
    *           Timeout in milliseconds for synchronous transactions to
    *           wait before automatically unblocking.  Use 0 to set an
    *           indefinite wait.
    *
    * @return   A network aware object that implements the given interfaces.
    *           All access to this local object will be redirected to the
    *           exported object on the other side of the network connection.
    */
   public static Object obtainNetworkInstance(Class<?>[] list, 
                                              Session session,
                                              int timeout)
   {      
      ClassLoader loader = RemoteObject.class.getClassLoader();
      
      if (session == null)
      {
         session = SessionManager.get().getSession();
      }
      
      RemoteAccess handler = new RemoteAccess(session, timeout);
      
      return Proxy.newProxyInstance(loader, list, handler);
   }
   
   /**
    * Convenience method which returns a unique instance of an object that 
    * implements the given interface.  Any method calls on this instance will
    * be EITHER:
    * <p>
    * <ol>
    *    <li> If there is a valid network session associated with this
    *         context, then the methods calls will be transparently executed 
    *         by the exported methods on the other side of the network 
    *         connection (message queue). This is the equivalent of calling
    *         {@link #obtainNetworkInstance}.
    *    <li> Otherwise a local instance is obtained. Method calls are
    *         transparently executed by the exported methods of the registered
    *         server in the SAME JVM. The fact of whether the server is an
    *         instance or static server is completely hidden by the
    *         implementation. This is the equivalent of calling
    *         {@link #obtainLocalInstance}.
    * </ol>
    * 
    * @param    cls
    *           The interface for which a local instance must be created.
    * @param    left
    *           <code>true</code> if the caller is considered the "left"
    *           side of the local session, <code>false</code> for the "right"
    *           side. This controls the direction of synchronization on method
    *           calls for the returned instance.
    *
    * @return   An object that implements the given interface or if there is no
    *           registered server for the given interface, <code>null</code> is
    *           returned.
    */
   public static Object obtainInstance(Class<?> cls, boolean left)
   {
      Session session  = SessionManager.get().getSession();
      Object  instance = RemoteObject.obtainLocalInstance(cls, left);
      
      if (session != null && instance == null)
      {
         instance = RemoteObject.obtainNetworkInstance(cls, session);
      }
      
      return instance;
   }
   
   /**
    * Sets the instance of the state synchronizer for the "left" side of a
    * "local" (same-JVM) proxy environment. When obtaining a local instance
    * the requester must specify if they are on the left or right side. This
    * is an arbitrary split that determines if the synchronizer is used as
    * the source or sink when a method call is made. When a left side requester
    * calls a method for the right side, then the left synchronizer is used
    * as the source and the right synchronizer is used as the sink. When a
    * right side requester calls a left side method, then the right synchronizer
    * is the source and the left synchronizer is the sink.  Synchronization is
    * only done once for each call and it is done before the method is invoked.
    *
    * @param   left
    *          The synchronizer to register or <code>null</code> if the
    *          currently registered synchronizer should be deregistered.
    */
   public static void registerLeftSynchronizer(StateSynchronizer left)
   {
      WorkArea wa = work.get();
      
      synchronized (wa)
      {
         wa.left = left;
      }
   }
   
   /**
    * Sets the instance of the state synchronizer for the "right" side of a
    * "local" (same-JVM) proxy environment. When obtaining a local instance
    * the requester must specify if they are on the left or right side. This
    * is an arbitrary split that determines if the synchronizer is used as
    * the source or sink when a method call is made. When a left side requester
    * calls a method for the right side, then the left synchronizer is used
    * as the source and the right synchronizer is used as the sink. When a
    * right side requester calls a left side method, then the right synchronizer
    * is the source and the left synchronizer is the sink.  Synchronization is
    * only done once for each call and it is done before the method is invoked.
    *
    * @param   right
    *          The synchronizer to register or <code>null</code> if the
    *          currently registered synchronizer should be deregistered.
    */
   public static void registerRightSynchronizer(StateSynchronizer right)
   {
      WorkArea wa = work.get();
      
      synchronized (wa)
      {
         wa.right = right;
      }
   }
   
   /**
    * Creates a unique instance of an object that implements the given 
    * interface and upon which any method calls will be transparently 
    * executed by the exported methods of the registered server in the
    * SAME JVM. The fact of whether the server is an instance or static
    * server is completely hidden by the implementation.
    * 
    * @param    cls
    *           The interface for which a local instance must be created.
    * @param    left
    *           <code>true</code> if the caller is considered the "left"
    *           side of the local session, <code>false</code> for the "right"
    *           side. This controls the direction of synchronization on method
    *           calls for the returned instance.
    *
    * @return   An object that implements the given interface. All access to
    *           this object will be redirected to the locally exported object
    *           within this JVM. If there is no registered server for the
    *           given interface, <code>null</code> is returned.
    */
   public static Object obtainLocalInstance(Class<?> cls, boolean left)
   {      
      ClassLoader         loader  = RemoteObject.class.getClassLoader();
      WorkArea            wa      = work.get();
      LocalStateSync      sync    = getSynchronizer(wa, left);
      InvocationHandler   handler = null;
      Map<Method, Method> map     = null;
      
      // check for a static server first
      synchronized (localStaticServers)
      {
         map = localStaticServers.get(cls);
      }
      
      if (map != null)
      {
         Class<?>[] list = new Class<?>[] { cls };
         Method[][] methods = calcExportedMethods(list);

         if (!Dispatcher.lockMethods(list, methods, null, false, false))
         {
            return null;
         }
         
         try
         {
            handler = new LocalStaticRedirector(map, sync);
         }
         finally
         {
            Dispatcher.releaseMethods(methods);
         }
      }
      else
      {
         Object      impl = null;
         Set<Method> set  = null;
         
         // now check for an instance server
         synchronized (wa)
         {
            impl = wa.localInstanceServers.get(cls);
            
            if (impl == null)
            {
               // no registered server matches the requested interface
               return null;
            }
            
            set = wa.localInstanceMethods.get(cls);
         }
         
         handler = new LocalRedirector(impl, set, sync);
      }
      
      return Proxy.newProxyInstance(loader, new Class<?>[] { cls }, handler);
   }
   
   /**
    * Find the matching <code>static</code> method in the given class that
    * corresponds to the given method.  A match is based on method name,
    * return type and parameter number/order/types.  All criteria must
    * match exactly.  Note that only the <code>static</code> modifier is
    * ignored in this comparison.
    *
    * @param    cls
    *           The class in which to search.
    * @param    method
    *           The instance method signature to match.
    *
    * @return   The matching method or <code>null</code> if no match was
    *           found.
    */
   public static Method findMatchingStaticMethod(Class<?> cls, Method method)
   {
      Method[]   target  = cls.getMethods();
      Class<?>[] parms   = method.getParameterTypes();
      Class<?>   rettype = method.getReturnType();
      String     name    = method.getName();

      outer:
      for (int i = 0; i < target.length; i++)
      {
         // check name, return type and that the method is static
         Method m = target[i];
         if (m.getName().equals(name)          &&
             m.getReturnType().equals(rettype) &&
             Modifier.isStatic(m.getModifiers()))
         {
            Class<?>[] possible = m.getParameterTypes();

            if (possible.length != parms.length)
            {
               // not a match
               continue outer;
            }

            // check parameters
            for (int j = 0; j < parms.length; j++)
            {
               if (!parms[j].equals(possible[j]))
               {
                  continue outer;
               }
            }

            // we found our result
            return m;
         }
      }

      return null;
   }

   /**
    * Return a unique group name for exporting this interface.
    *
    * @param   iface
    *          The interface to be exported.
    *
    * @return   The group name that is unique to this interface.
    */
   static String calcGroupName(Class<?> iface)
   {
      return iface.getName();
   }

   /**
    * Configure and return an instance of the local state synchronizer.
    *
    * @param    wa
    *           Context-local state. 
    * @param    leftSide
    *           <code>true</code> if the caller is considered the "left"
    *           side of the local session, <code>false</code> for the "right"
    *           side. This controls the direction of synchronization on method
    *           calls for the returned instance.
    *
    * @return   The properly configured state synchronizer or <code>null</code>
    *           if state synchronization is not enabled.
    */
   private static LocalStateSync getSynchronizer(WorkArea wa, boolean leftSide)
   {
      LocalStateSync    sync  = null;
      StateSynchronizer left  = null;
      StateSynchronizer right = null;
      
      synchronized (wa)
      {
         left  = wa.left;
         right = wa.right;
      }
      
      if (left != null && right != null)
      {
         sync = new LocalStateSync(leftSide ? left  : right,
                                   leftSide ? right : left);
      }
      
      return sync;
   }
   
   /**
    * If logging is enabled at <code>FINER</code> or greater detail, log
    * that an export has been created for the given group and method name.
    *
    * @param    group
    *           Group name of the export.
    * @param    name
    *           Method name of the export.
    */
   private static void logExport(String group, String name)
   {
      if (LOG.isLoggable(Level.FINER))
      {
         LOG.finer("exported " + group + ':' + name);
      }
   }
   
   /**
    * Return the list of methods for all interfaces in the given list.
    *
    * @param   list
    *          The list of interfaces to be exported.
    *
    * @return   The methods to export, grouped by interface.
    */
   private static Method[][] calcExportedMethods(Class<?>[] list)
   {
      Method[][] methods = new Method[list.length][];
      for (int i = 0; i < list.length; i++)
      {
         methods[i] = calcExportedMethods(list[i]);
      }
      
      return methods;
   }
   
   /**
    * Return a list of methods in a deterministic order such that for the
    * same interface (with identical methods) the list will be identical
    * on both the server and client.
    *
    * @param   iface
    *          The interface to be exported.
    *
    * @return   The list of methods to export.
    */
   private static Method[] calcExportedMethods(Class<?> iface)
   {
      // actually, no sorting is needed since the client and server
      // rendezvous using a shared name (that we calculate using the method
      // object on both sides)
      return iface.getMethods();            
   }
   
   /**
    * Return a unique method name corresponding to this given method.
    *
    * @param    method
    *           The method for which a name must be calculated.
    *
    * @return   The name that is unique to this method.
    */
   static String calcKeyName(Method method)
   {
      return method.toString();
   }   
   
   /**
    * Provides the proxy implementation for a remote object.  This handles
    * all lookup and dispatching for the remote side of the given interface. 
    */
   private static class RemoteAccess
   extends InvocationStub
   {
      /** Routing key cache mapped by method object. */
      private Map<Method, RoutingKey> keylist =
         new HashMap<Method, RoutingKey>();
         
      /** Remote invocation helper. */
      private HighLevelObject hlo = null;
      
      /**
       * Creates a unique instance of an object that can handle invocations 
       * of any proxied method by redirecting the method calls to a remote
       * node's exported API and returning the result or throwing any
       * exceptions as needed.
       *
       * @param    session
       *           The network connection which the returned object should
       *           represent.  Must not be <code>null</code>.
       * @param    timeout
       *           Timeout in milliseconds for synchronous transactions to
       *           wait before automatically unblocking.  Use 0 to set an
       *           indefinite wait.
       */      
      RemoteAccess(Session session, int timeout)
      {
         this.hlo = new HighLevelObject(session, timeout);
      }
      
      /**
       * This method acts as an interceptor which is called every time a
       * method of the proxy object is invoked. It allows us to perform pre-
       * and post-processing for these methods, and to make the remote
       * method call to the exported interface (over the network to the
       * other node).
       *
       * @param    proxy
       *           Proxy object upon which the intercepted method is invoked.
       * @param    method
       *           Method to be invoked through reflection.
       * @param    args
       *           Arguments, if any, to <code>method</code>.
       *
       * @return   Result of remote call.
       *
       * @throws   Throwable
       *           if an exception/error occurs upon the invocation target,
       *           or if any problem occurs during pre- or post-processing.
       */
      @Override
      public Object invokeCore(Object proxy, Method method, Object[] args)
      throws Throwable
      {
         // lookup the cached key or obtain a new key (the first time this
         // is invoked, it will cause a lookup transaction with the other
         // side whose result will be cached)
         RoutingKey key = obtainRoutingKey(method, proxy);

         if (key == null)
         {
            throw new SilentUnwindException();
         }
         
         // execute the remote method and return the result
         return LOG.isLoggable(Level.FINER) ?
            trace(proxy, method, args, key) :
            hlo.transact(key, args);
      }
      
      /**
       * Tracing worker that handles the remote method call to the exported
       * interface (over the network to the other node) 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    proxy
       *           Proxy object upon which the intercepted method is invoked.
       * @param    method
       *           Method to be invoked through reflection.
       * @param    args
       *           Arguments, if any, to <code>method</code>.
       * @param    key
       *           The routing key used for the remote method invocation.
       *
       * @return   Result of remote call.
       * 
       * @throws   Throwable
       *           If an exception/error occurs upon the invocation target,
       *           or if any problem occurs during pre- or post-processing.
       */
      public Object trace(Object     proxy,
                          Method     method,
                          Object[]   args,
                          RoutingKey key)
      throws Throwable
      {
         return TraceHelper.trace(new Invoker(key, calcKeyName(method)),
                                  args,
                                  LOG,
                                  "RemoteObject.invoke()");
      }
      
      /**
       * Gets the routing key associated with a specific method.  If this
       * key has been previously found it will be obtained via a cache,
       * otherwise it will be looked up upon first request (a type of lazy
       * initialization).
       * 
       * @param    method
       *           The method for which the key must be obtained.
       *
       * @return   The routing key which corresponds to the given method.
       */
      private RoutingKey obtainRoutingKey(Method method, Object proxy)
      {
         RoutingKey key = keylist.get(method);
         
         // check if this is the first use
         if (key == null)
         {
            // the method's declaring interface may not be the same as the one
            // used as the group name when this object was registered, so we
            // have to find the most specific interface which includes the
            // method (i.e. via extending the declaring interface)
            Class<?> match = null;
            for (Class<?> next : proxy.getClass().getInterfaces())
            {
               if (hierarchyDeclaresMethod(method, next))
               {
                  if (match != null)
                  {
                     throw new RuntimeException(
                        "Ambiguous remote method (" + method.getName() + ")");
                  }
                  
                  match = next;
               }
            }
            
            String group = calcGroupName(match);
            String name  = calcKeyName(method);
            
            try
            {
               // lookup the key
               key = hlo.getKey(group, name);
               
               if (key == null)
                  return null;
               
               // cache the found key
               keylist.put(method, key);
            }
            catch (@SuppressWarnings("unused") IllegalStateException ise)
            {
               return null;
            }
            catch (Exception exc)
            {
               // check if the cause is a InterruptedException. in this case,
               // it should handled
               Throwable cause = exc;
               while (cause != null)
               {
                  if (cause instanceof InterruptedException)
                  {
                     Control.handleInterrupted((InterruptedException) cause);
                     break;
                  }

                  cause = cause.getCause();
               }

               throw new RuntimeException("Unresolvable remote export " + 
                                          name + ".",
                                          exc);
            }
         }
         
         return key;
      }
      
      /**
       * Determine whether the given interface or any of its ancestors
       * declare the given method.  This method is recursive.
       * 
       * @param   method
       *          Method to search for.
       * @param   iface
       *          Interface whose inheritance hierarchy is to be searched.
       *          
       * @return  <code>true</code> if <code>iface</code> or any of its
       *          ancestor superinterfaces declares <code>method</code>, else
       *          <code>false</code>.
       */
      private boolean hierarchyDeclaresMethod(Method method, Class<?> iface)
      {
         if (iface.equals(method.getDeclaringClass()))
         {
            return true;
         }
         
         for (Class<?> next : iface.getInterfaces())
         {
            if (hierarchyDeclaresMethod(method, next))
            {
               return true;
            }
         }
         
         return false;
      }
   
      /**
       * Helper class to delegate execution of the <code>transact</code>
       * method.
       */
      private class Invoker
      implements Invocable
      {
         /** Routing key for the <code>transact</code> method. */
         private RoutingKey key = null;
         
         /** The description of the method being executed. */
         private String description = null;
         
         /**
          * Construct an instance that can make an invocation of a specific
          * remote method.
          *
          * @param    key
          *           The routing key to use.
          * @param    description
          *           The description of the method being invoked.
          */
         public Invoker(RoutingKey key, String description)
         {
            this.key         = key;
            this.description = description;
         }
         
         /**
          * Return a text description of the method being invoked.
          *
          * @return   The description.
          */
         @Override
         public String describe()
         {
            return description;
         }
         
         /**
          * Execute or "run" the object's functionality. 
          *
          * @param    args
          *           The method arguments or <code>null</code> for no arguments.
          *
          * @return   The called method's return value or <code>null</code> if
          *           there is no return value.  Note that this design does not 
          *           allow the caller to determine the difference between a
          *           <code>void</code> return and a return of a genuine
          *           <code>null</code>.
          *
          * @throws   Throwable
          *           If the called method generates any exception or error.
          */
         @Override
         public Object invoke(Object[] args)
         throws Throwable
         {
            return hlo.transact(key, args);
         }
      }
   }
   
   /** 
    * Stores global data relating to the state of the current context.
    */
   private static class WorkArea
   {  
      /** Storage for local instance method maps. */
      private Map<Class<?>, Set<Method>> localInstanceMethods = new HashMap<>();
         
      /** Storage for local instance servers. */
      private Map<Class<?>, Object> localInstanceServers = new HashMap<>();
         
      /** Authenticates the caller as allowed to modify registrations. */
      private HashMap<Class<?>, Object> modTokens = new HashMap<>();

      /** Left side state synchronizer. */
      private StateSynchronizer left = null;
         
      /** Right side state synchronizer. */
      private StateSynchronizer right = null;
         
      /** Left side is the source and right is the sink. */ 
      private LocalStateSync leftToRight = null;
      
      /** Right side is the source and left is the sink. */ 
      private LocalStateSync rightToLeft = null;
   }
}