// // $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. * *

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). * *

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 Switch, the response * may be delivered via a call to the handleSwitchSuccess * 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(); }