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;
}
}