Files
narya/src/java/com/threerings/presents/client/InvocationDirector.java
T
Michael Bayne 6df0ef6172 We need to do some fiddling when the invocation director is used on the
server by entities that are vaguely impersonating a client. Specifically
they need to fill in the event's source oid because their events are not
going through the client networking layer which takes care of filling that
in for real clients.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1261 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-04-16 21:37:23 +00:00

307 lines
12 KiB
Java

//
// $Id: InvocationDirector.java,v 1.20 2002/04/16 21:37:23 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.samskivert.util.ResultListener;
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 MessageListener
{
/**
* Initializes the invocation director.
*
* @param omgr the distributed object manager via which the invocation
* manager will send and receive events.
* @param cloid the oid of the object on which invocation
* notifications as well as invocation responses will be received.
* @param imoid the oid of the object on the server to which to
* deliver invocation requests.
* @param initListener a result listener that will be notified when
* the invocation director is up and running (meaning it has
* subscribed successfully to the client object and is ready to
* process invocation requests); or when initialization has failed.
*/
public void init (DObjectManager omgr, final int cloid, int imoid,
final ResultListener initListener)
{
_omgr = omgr;
_imoid = imoid;
_cloid = cloid;
// add ourselves as a subscriber to the client object
_omgr.subscribeToObject(cloid, new Subscriber() {
public void objectAvailable (DObject object)
{
// add ourselves as a message listener
object.addListener(InvocationDirector.this);
// let the client know that we're ready to go now that
// we've got our subscription to the client object
initListener.requestCompleted(object);
}
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 [cloid=" + cloid + "]!");
initListener.requestFailed(cause);
}
});
}
/**
* 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);
}
// because invocation directors are used on the server, we set the
// source oid here so that invocation requests are properly
// attributed to the right client object when created by
// server-side entities only sort of pretending to be a client
event.setSourceOid(_cloid);
// 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)
{
if (_receivers == null) {
_receivers = new HashMap();
}
_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)
{
if (_receivers != null) {
_receivers.remove(module);
}
}
/**
* 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 {
// Log.info("Invoking method [meth=" +
// rsptarg.getClass().getName() + "." + rspmeth.getName() +
// ", args=" + StringUtil.toString(rargs) + "].");
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 = null;
if (_receivers != null) {
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 + "].");
Log.logStackTrace(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 DObjectManager _omgr;
protected int _imoid;
protected int _cloid;
protected int _invocationId;
protected HashIntMap _targets = new HashIntMap();
protected HashMap _receivers = new HashMap();
}