f506a686f9
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@650 542714f4-19e9-0310-aa3c-eee0fc999fb1
283 lines
10 KiB
Java
283 lines
10 KiB
Java
//
|
|
// $Id: InvocationDirector.java,v 1.16 2001/11/27 21:37:58 mdb Exp $
|
|
|
|
package com.threerings.presents.client;
|
|
|
|
import java.lang.reflect.Method;
|
|
import java.util.HashMap;
|
|
|
|
import com.samskivert.util.HashIntMap;
|
|
import com.samskivert.util.StringUtil;
|
|
|
|
import com.threerings.presents.Log;
|
|
import com.threerings.presents.data.*;
|
|
import com.threerings.presents.dobj.*;
|
|
import com.threerings.presents.util.ClassUtil;
|
|
|
|
/**
|
|
* The invocation services provide client to server invocations (service
|
|
* requests) and server to client invocations (responses and
|
|
* notifications). Via this mechanism, the client can make requests of the
|
|
* server, be notified of its response and the server can asynchronously
|
|
* invoke code on the client.
|
|
*
|
|
* <p> Invocations are like remote procedure calls in that they are named
|
|
* and take arguments. They are simple in that the arguments can only be
|
|
* of a small set of supported types (the set of distributed object field
|
|
* types) and there is no special facility provided for referencing
|
|
* non-local objects (it is assumed that the distributed object facility
|
|
* will already be in use for any objects that should be shared).
|
|
*
|
|
* <p> The client invocation director delivers invocation requests to the
|
|
* server invocation manager and maps the responses back to the proper
|
|
* response target objects when they arrive. It also maintains the mapping
|
|
* of invocation receivers that can receive asynchronous invocation
|
|
* notifications at any time from the server.
|
|
*/
|
|
public class InvocationDirector
|
|
implements Subscriber, MessageListener
|
|
{
|
|
/**
|
|
* Initializes the invocation director with the specified invocation
|
|
* manager oid. It will obtain its distributed object manager and
|
|
* client object references from the supplied client instance. The
|
|
* invocation manager oid is the oid of the object on the server to
|
|
* which to deliver invocation requests.
|
|
*/
|
|
public void init (Client client, int imoid)
|
|
{
|
|
_client = client;
|
|
_omgr = client.getDObjectManager();
|
|
_imoid = imoid;
|
|
|
|
// add ourselves as a subscriber to the client object
|
|
_omgr.subscribeToObject(client.getClientOid(), this);
|
|
}
|
|
|
|
/**
|
|
* Sends an invocation request to the server. If a response target is
|
|
* supplied, the response will be delivered to that object by calling
|
|
* a member function on it whose name is defined in the response
|
|
* generated by the invocation implementation. In general, this is a
|
|
* derivative of the invocation procedure name. For example, if the
|
|
* caller invoked a procedure named <code>Switch</code>, the response
|
|
* may be delivered via a call to the <code>handleSwitchSuccess</code>
|
|
* method on the response target object. The signature of that method
|
|
* would be defined by the arguments provided in the response message
|
|
* with the addition of a first argument which is the invocation
|
|
* identifier for this particular request (that is also returned by
|
|
* this function).
|
|
*
|
|
* @param module the name of the invocation module to use.
|
|
* @param procedure the name of the procedure within that module.
|
|
* @param args the arguments of the invocation.
|
|
* @param rsptarget the object that will receive the response, or null
|
|
* if no response is desired.
|
|
*
|
|
* @return a unique identifier associated with this invocation
|
|
* request. This identifier will be passed as the first argument to
|
|
* the response function.
|
|
*/
|
|
public int invoke (String module, String procedure, Object[] args,
|
|
Object rsptarget)
|
|
{
|
|
int invid = nextInvocationId();
|
|
|
|
// if null arguments were supplied, assume zero arguments
|
|
if (args == null) {
|
|
args = new Object[0];
|
|
}
|
|
|
|
// we need an args array for a message that can contain the
|
|
// invocation names, an invocation id and the invocation arguments
|
|
Object[] iargs = new Object[args.length+3];
|
|
iargs[0] = module;
|
|
iargs[1] = procedure;
|
|
iargs[2] = new Integer(invid);
|
|
System.arraycopy(args, 0, iargs, 3, args.length);
|
|
|
|
// create a message event on the invocation manager object
|
|
MessageEvent event = new MessageEvent(
|
|
_imoid, InvocationObject.REQUEST_NAME, iargs);
|
|
|
|
// if we have a response target, register that for later receipt
|
|
// of the response
|
|
if (rsptarget != null) {
|
|
_targets.put(invid, rsptarget);
|
|
}
|
|
|
|
// and finally ship off the invocation message
|
|
_omgr.postEvent(event);
|
|
|
|
return invid;
|
|
}
|
|
|
|
/**
|
|
* Registers the supplied invocation receiver instance as the handler
|
|
* for all invocation notifications for the specified module.
|
|
*/
|
|
public void registerReceiver (String module, InvocationReceiver receiver)
|
|
{
|
|
_receivers.put(module, receiver);
|
|
}
|
|
|
|
/**
|
|
* Removes the registration for the supplied invocation receiver
|
|
* instance as the handler for invocation notifications for the
|
|
* specified module.
|
|
*/
|
|
public void unregisterReceiver (String module)
|
|
{
|
|
_receivers.remove(module);
|
|
}
|
|
|
|
// documentation inherited
|
|
public void objectAvailable (DObject object)
|
|
{
|
|
// add ourselves as a message listener
|
|
object.addListener(this);
|
|
|
|
// let the client know that we're ready to go now that we've got
|
|
// our subscription to the client object
|
|
_client.invocationDirectorReady((ClientObject)object);
|
|
}
|
|
|
|
// documentation inherited
|
|
public void requestFailed (int oid, ObjectAccessException cause)
|
|
{
|
|
// aiya! we were unable to subscribe to the client object. we're
|
|
// hosed, hosed, hosed
|
|
Log.warning("Invocation director unable to subscribe to client " +
|
|
"object. All is wrong in the universe.");
|
|
}
|
|
|
|
/**
|
|
* Process incoming message requests on user object.
|
|
*/
|
|
public void messageReceived (MessageEvent event)
|
|
{
|
|
String name = event.getName();
|
|
if (name.equals(InvocationObject.RESPONSE_NAME)) {
|
|
handleInvocationResponse(event.getArgs());
|
|
} else if (name.equals(InvocationObject.NOTIFICATION_NAME)) {
|
|
handleInvocationNotification(event.getArgs());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes an invocation response message.
|
|
*/
|
|
protected void handleInvocationResponse (Object[] args)
|
|
{
|
|
String name = (String)args[0];
|
|
int invid = ((Integer)args[1]).intValue();
|
|
|
|
Object rsptarg = _targets.get(invid);
|
|
if (rsptarg == null) {
|
|
Log.warning("No target for invocation response " +
|
|
"[args=" + StringUtil.toString(args) + "].");
|
|
return;
|
|
}
|
|
|
|
// prune the invocation id and method arguments from the full
|
|
// message arguments
|
|
Object[] rargs = new Object[args.length-1];
|
|
System.arraycopy(args, 1, rargs, 0, rargs.length);
|
|
|
|
// and invoke the response method; we'd cache these but the key
|
|
// for the method would have to include all of the class names of
|
|
// all of the arguments and would probably be more expensive to
|
|
// create than just reflecting the method (we should really test
|
|
// this because that's half intuition and half wild-ass guess, but
|
|
// we're going with it for now)
|
|
String mname = "handle" + name;
|
|
Method rspmeth = ClassUtil.getMethod(mname, rsptarg, rargs);
|
|
if (rspmeth == null) {
|
|
Log.warning("Unable to resolve response method " +
|
|
"[target=" + rsptarg.getClass().getName() +
|
|
", method=" + mname +
|
|
", args=" + StringUtil.toString(rargs) + "].");
|
|
return;
|
|
}
|
|
|
|
// and invoke it
|
|
try {
|
|
rspmeth.invoke(rsptarg, rargs);
|
|
} catch (Exception e) {
|
|
Log.warning("Error invoking response target method " +
|
|
"[target=" + rsptarg + ", method=" + rspmeth + "].");
|
|
Log.logStackTrace(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes an invocation notification message.
|
|
*/
|
|
protected void handleInvocationNotification (Object[] args)
|
|
{
|
|
String module = (String)args[0];
|
|
String proc = (String)args[1];
|
|
|
|
InvocationReceiver receiver =
|
|
(InvocationReceiver)_receivers.get(module);
|
|
if (receiver == null) {
|
|
Log.warning("No receiver registered for notification " +
|
|
"[args=" + StringUtil.toString(args) + "].");
|
|
return;
|
|
}
|
|
|
|
// prune the method arguments from the full message arguments
|
|
Object[] nargs = new Object[args.length-2];
|
|
System.arraycopy(args, 2, nargs, 0, nargs.length);
|
|
|
|
// and invoke the receiver method; we'd cache these but the key
|
|
// for the method would have to include all of the class names of
|
|
// all of the arguments and would probably be more expensive to
|
|
// create than just reflecting the method (we should really test
|
|
// this because that's half intuition and half wild-ass guess, but
|
|
// we're going with it for now)
|
|
String mname = "handle" + proc + "Notification";
|
|
Method rspmeth = ClassUtil.getMethod(mname, receiver, nargs);
|
|
if (rspmeth == null) {
|
|
Log.warning("Unable to resolve receiver method " +
|
|
"[target=" + receiver.getClass().getName() +
|
|
", method=" + mname +
|
|
", args=" + StringUtil.toString(nargs) + "].");
|
|
return;
|
|
}
|
|
|
|
// and invoke it
|
|
try {
|
|
rspmeth.invoke(receiver, nargs);
|
|
} catch (Exception e) {
|
|
Log.warning("Error invoking receiver method " +
|
|
"[receiver=" + receiver + ", method=" + rspmeth +
|
|
", error=" + e + "].");
|
|
}
|
|
}
|
|
|
|
public synchronized int nextInvocationId ()
|
|
{
|
|
return _invocationId++;
|
|
}
|
|
|
|
protected static class Response
|
|
{
|
|
public String name;
|
|
public Object target;
|
|
public Response (String name, Object target)
|
|
{
|
|
this.name = name;
|
|
this.target = target;
|
|
}
|
|
}
|
|
|
|
protected Client _client;
|
|
protected DObjectManager _omgr;
|
|
protected int _imoid;
|
|
|
|
protected int _invocationId;
|
|
protected HashIntMap _targets = new HashIntMap();
|
|
protected HashMap _receivers = new HashMap();
|
|
}
|