diff --git a/src/java/com/threerings/presents/client/InvocationDirector.java b/src/java/com/threerings/presents/client/InvocationDirector.java index 74078fc91..9277cf559 100644 --- a/src/java/com/threerings/presents/client/InvocationDirector.java +++ b/src/java/com/threerings/presents/client/InvocationDirector.java @@ -1,11 +1,13 @@ // -// $Id: InvocationDirector.java,v 1.3 2001/07/19 18:08:20 mdb Exp $ +// $Id: InvocationDirector.java,v 1.4 2001/07/19 19:18:06 mdb Exp $ package com.threerings.cocktail.cher.client; import java.lang.reflect.Method; import java.util.HashMap; +import com.samskivert.util.StringUtil; + import com.threerings.cocktail.cher.Log; import com.threerings.cocktail.cher.data.*; import com.threerings.cocktail.cher.dobj.*; @@ -84,7 +86,7 @@ public class InvocationManager // create a message event on the invocation manager object MessageEvent event = new MessageEvent( - _imoid, InvocationObject.MESSAGE_NAME, iargs); + _imoid, InvocationObject.REQUEST_NAME, iargs); // if we have a response target, register that for later receipt // of the response @@ -96,6 +98,15 @@ public class InvocationManager _omgr.postEvent(event); } + /** + * 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); + } + public void objectAvailable (DObject object) { // let the client know that we're ready to go now that we've got @@ -123,21 +134,29 @@ public class InvocationManager // and only those of proper name MessageEvent mevt = (MessageEvent)event; - if (!mevt.getName().equals(InvocationObject.MESSAGE_NAME)) { - return true; + String name = mevt.getName(); + if (name.equals(InvocationObject.RESPONSE_NAME)) { + handleInvocationResponse(mevt.getArgs()); + } else if (name.equals(InvocationObject.NOTIFICATION_NAME)) { + handleInvocationNotification(mevt.getArgs()); } - // we've got an invocation response, so we extract the args and - // process it - Object[] args = mevt.getArgs(); + return true; + } + + /** + * 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 " + - "[rsp=" + mevt + "]."); - return true; + "[args=" + StringUtil.toString(args) + "]."); + return; } // prune the method arguments from the full message arguments @@ -151,7 +170,7 @@ public class InvocationManager Log.warning("Unable to resolve response method " + "[target=" + rsptarg.getClass().getName() + ", method=" + mname + "]."); - return true; + return; } // and invoke it @@ -162,8 +181,46 @@ public class InvocationManager "[target=" + rsptarg + ", method=" + rspmeth + ", error=" + e + "]."); } + } - return true; + /** + * 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 + String mname = "handle" + proc + "Notification"; + Method rspmeth = ClassUtil.getMethod(mname, receiver, _methcache); + if (rspmeth == null) { + Log.warning("Unable to resolve receiver method " + + "[target=" + receiver.getClass().getName() + + ", method=" + mname + "]."); + return; + } + + // and invoke it + try { + rspmeth.invoke(receiver, nargs); + } catch (Exception e) { + Log.warning("Error invoking receiver method " + + "[receiver=" + receiver + ", method=" + rspmeth + + ", error=" + e + "]."); + } } protected synchronized int nextInvocationId () @@ -189,5 +246,6 @@ public class InvocationManager protected int _invocationId; protected IntMap _targets = new IntMap(); + protected HashMap _receivers = new HashMap(); protected HashMap _methcache = new HashMap(); } diff --git a/src/java/com/threerings/presents/client/InvocationReceiver.java b/src/java/com/threerings/presents/client/InvocationReceiver.java new file mode 100644 index 000000000..301fb234f --- /dev/null +++ b/src/java/com/threerings/presents/client/InvocationReceiver.java @@ -0,0 +1,34 @@ +// +// $Id: InvocationReceiver.java,v 1.1 2001/07/19 19:18:06 mdb Exp $ + +package com.threerings.cocktail.cher.client; + +/** + * Classes registered to process invocation notifications should extend + * the invocation receiver class and register themselves with the + * invocation manager. Because the invocation notification procedures are + * looked up using reflection, there are no methods to override in the + * receiver class, but it serves as a useful point for documentation and + * as a useful indicator that the derived class in question is actually + * serving as an invocation receiver. + * + *
Invocation notifications are identified by a module name and a
+ * procedure name. The module name identifies which invocation receiver
+ * instance will receive the notification. Receivers are registered with
+ * the invocation manager as handling all notification procedures for a
+ * particular module. The notification procedure name is used to construct
+ * a method name which is then reflected and invoked. The name
+ * construction is as follows: a notification message requesting the
+ * invocation of a procedure named Tell will result in a
+ * method named handleTellNotification being invoked on the
+ * invocation receiver instance. The signature of that method is defined
+ * by the arguments supplied with the invocation notification message.
+ * These arguments must always be of the same type and must exactly match
+ * the signature of the implementing method (with the standard reflection
+ * argument type conversion process taken into account).
+ *
+ * @see InvocationManager#registerReceiver
+ */
+public class InvocationReceiver
+{
+}
diff --git a/src/java/com/threerings/presents/data/InvocationObject.java b/src/java/com/threerings/presents/data/InvocationObject.java
index 63eff4622..4d0e56c0e 100644
--- a/src/java/com/threerings/presents/data/InvocationObject.java
+++ b/src/java/com/threerings/presents/data/InvocationObject.java
@@ -1,5 +1,5 @@
//
-// $Id: InvocationObject.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
+// $Id: InvocationObject.java,v 1.2 2001/07/19 19:18:06 mdb Exp $
package com.threerings.cocktail.cher.data;
@@ -14,8 +14,20 @@ import com.threerings.cocktail.cher.dobj.DObject;
public class InvocationObject extends DObject
{
/**
- * This constant is used to identify messages on both ends of the
- * invocation services.
+ * This constant is used to identify invocation requests sent to the
+ * server.
*/
- public static final String MESSAGE_NAME = "invoke";
+ public static final String REQUEST_NAME = "invreq";
+
+ /**
+ * This constant is used to identify invocation responses sent to the
+ * client.
+ */
+ public static final String RESPONSE_NAME = "invrsp";
+
+ /**
+ * This constant is used to identify invocation notifications sent to
+ * the client.
+ */
+ public static final String NOTIFICATION_NAME = "invnot";
}
diff --git a/src/java/com/threerings/presents/server/InvocationManager.java b/src/java/com/threerings/presents/server/InvocationManager.java
index 9d2f02611..f1afc9db4 100644
--- a/src/java/com/threerings/presents/server/InvocationManager.java
+++ b/src/java/com/threerings/presents/server/InvocationManager.java
@@ -1,5 +1,5 @@
//
-// $Id: InvocationManager.java,v 1.4 2001/07/19 18:08:20 mdb Exp $
+// $Id: InvocationManager.java,v 1.5 2001/07/19 19:18:07 mdb Exp $
package com.threerings.cocktail.cher.server;
@@ -56,6 +56,39 @@ public class InvocationManager
_providers.put(module, provider);
}
+ /**
+ * Delivers an invocation notification to the specified client. The
+ * module argument selects which
+ * InvocationReceiver will be invoked and the
+ * procedure argument indicates which method will be
+ * invoked on that receiver.
+ *
+ *
The method is constructed as follows: a procedure name of
+ * Tell will result in a method call to
+ * handleTellNotification. The arguments provided with
+ * the notification define the necessary signature of that method,
+ * according to the argument conversion rules defined by the
+ * reflection services (Integer is converted to
+ * int, etc.).
+ */
+ public void sendNotification (
+ int cloid, String module, String procedure, Object[] args)
+ {
+ // package up the arguments
+ int alength = (args != null) ? args.length : 0;
+ Object[] nargs = new Object[alength + 2];
+ nargs[0] = module;
+ nargs[1] = procedure;
+ if (args != null) {
+ System.arraycopy(args, 0, nargs, 2, alength);
+ }
+
+ // construct a message event and deliver it
+ MessageEvent nevt = new MessageEvent(
+ cloid, InvocationObject.NOTIFICATION_NAME, nargs);
+ CherServer.omgr.postEvent(nevt);
+ }
+
public void objectAvailable (DObject object)
{
// this must be our invocation object
@@ -82,7 +115,7 @@ public class InvocationManager
// make sure the name is proper just for sanity's sake
MessageEvent mevt = (MessageEvent)event;
- if (!mevt.getName().equals(InvocationObject.MESSAGE_NAME)) {
+ if (!mevt.getName().equals(InvocationObject.REQUEST_NAME)) {
return true;
}
@@ -102,8 +135,17 @@ public class InvocationManager
}
// prune the method arguments from the full message arguments
- Object[] margs = new Object[args.length-3];
- System.arraycopy(args, 3, margs, 0, margs.length);
+ Object[] margs = new Object[args.length-2];
+ int cloid = mevt.getSourceOid();
+ margs[0] = CherServer.omgr.getObject(cloid);
+ // make sure the client is still around
+ if (margs[0] == null) {
+ Log.warning("Client no longer around for invocation provider " +
+ "request [module=" + module +
+ ", proc=" + procedure + ", cloid=" + cloid + "].");
+ return true;
+ }
+ System.arraycopy(args, 3, margs, 1, args.length-3);
// look up the method that will handle this procedure
String mname = "handle" + procedure + "Request";
@@ -131,7 +173,7 @@ public class InvocationManager
rargs[1] = invid;
// and create a message event for delivery to the client
MessageEvent revt = new MessageEvent(
- mevt.getSourceOid(), InvocationObject.MESSAGE_NAME, rargs);
+ mevt.getSourceOid(), InvocationObject.RESPONSE_NAME, rargs);
// and ship it off
CherServer.omgr.postEvent(revt);
}
diff --git a/src/java/com/threerings/presents/server/InvocationProvider.java b/src/java/com/threerings/presents/server/InvocationProvider.java
index 29fee2056..0a3609d63 100644
--- a/src/java/com/threerings/presents/server/InvocationProvider.java
+++ b/src/java/com/threerings/presents/server/InvocationProvider.java
@@ -1,5 +1,5 @@
//
-// $Id: InvocationProvider.java,v 1.1 2001/07/19 18:08:20 mdb Exp $
+// $Id: InvocationProvider.java,v 1.2 2001/07/19 19:18:07 mdb Exp $
package com.threerings.cocktail.cher.server;
@@ -12,9 +12,27 @@ package com.threerings.cocktail.cher.server;
* procedure identified by the name Tell would result in the
* invocation of a method named handleTellRequest. The
* arguments to that method would be defined by the arguments that
- * accompanied the Tell invocation request. If the arguments
- * do not match, a reflection error will happen when trying to invoke the
- * method and the whole request will fail.
+ * accompanied the Tell invocation request along with the
+ * client object of the client that made the request. Specifically:
+ *
+ *
+ * // client makes request
+ * Object[] args = new Object[] { "one", new Integer(2) };
+ * invmgr.invoke(MODULE, "Test", args, rsptarget);
+ *
+ * // provider registered for MODULE should look like:
+ * public class TestProvider extends InvocationProvider
+ * {
+ * public Object[] handleTestRequest (ClientObject source,
+ * String one, int two)
+ * {
+ * // ...
+ * }
+ * }
+ *
+ *
+ * If the arguments do not match, a reflection error will happen when
+ * trying to invoke the method and the whole request will fail.
*
* Invocation procedures must also package up their response in a
* particular way which is through the use of the
diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
index 19838fd3a..79c8c0d46 100644
--- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
+++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
@@ -1,5 +1,5 @@
//
-// $Id: PresentsDObjectMgr.java,v 1.6 2001/06/13 05:17:55 mdb Exp $
+// $Id: PresentsDObjectMgr.java,v 1.7 2001/07/19 19:18:07 mdb Exp $
package com.threerings.cocktail.cher.server;
@@ -74,6 +74,20 @@ public class CherDObjectMgr implements DObjectManager
// nothing to do here, our objects live forever!
}
+ /**
+ * Returns the object in the object table with the specified oid or
+ * null if no object has that oid. Be sure only to call this function
+ * from the dobjmgr thread and not to do anything funny with the
+ * object. If subscription is desired, use
+ * subscribeToObject().
+ *
+ * @see #subscribeToObject
+ */
+ public DObject getObject (int oid)
+ {
+ return (DObject)_objects.get(oid);
+ }
+
/**
* Runs the dobjmgr event loop until it is requested to exit. This
* should be called from the main application thread.
diff --git a/src/java/com/threerings/presents/server/PresentsServer.java b/src/java/com/threerings/presents/server/PresentsServer.java
index 0e970d86c..a66b45f8b 100644
--- a/src/java/com/threerings/presents/server/PresentsServer.java
+++ b/src/java/com/threerings/presents/server/PresentsServer.java
@@ -1,5 +1,5 @@
//
-// $Id: PresentsServer.java,v 1.7 2001/07/19 07:48:25 mdb Exp $
+// $Id: PresentsServer.java,v 1.8 2001/07/19 19:18:07 mdb Exp $
package com.threerings.cocktail.cher.server;
@@ -28,7 +28,7 @@ public class CherServer
public static ClientManager clmgr;
/** The distributed object manager. */
- public static DObjectManager omgr;
+ public static CherDObjectMgr omgr;
/** The invocation manager. */
public static InvocationManager invmgr;
diff --git a/tests/src/java/com/threerings/presents/client/TestClient.java b/tests/src/java/com/threerings/presents/client/TestClient.java
index a13311ada..404051ba7 100644
--- a/tests/src/java/com/threerings/presents/client/TestClient.java
+++ b/tests/src/java/com/threerings/presents/client/TestClient.java
@@ -1,5 +1,5 @@
//
-// $Id: TestClient.java,v 1.6 2001/07/19 18:08:20 mdb Exp $
+// $Id: TestClient.java,v 1.7 2001/07/19 19:18:06 mdb Exp $
package com.threerings.cocktail.cher.client.test;
@@ -38,6 +38,9 @@ public class TestClient
Log.info("Client did logon [client=" + client + "].");
// try subscribing to a test object
client.getDObjectManager().subscribeToObject(2, this);
+ // register our test notification receiver
+ client.getInvocationManager().registerReceiver(TestService.MODULE,
+ new TestReceiver());
// issue a test invocation request
TestService.test(client, "foo", 1, this);
}
diff --git a/tests/src/java/com/threerings/presents/client/TestReceiver.java b/tests/src/java/com/threerings/presents/client/TestReceiver.java
new file mode 100644
index 000000000..22b4d81d6
--- /dev/null
+++ b/tests/src/java/com/threerings/presents/client/TestReceiver.java
@@ -0,0 +1,16 @@
+//
+// $Id: TestReceiver.java,v 1.1 2001/07/19 19:18:06 mdb Exp $
+
+package com.threerings.cocktail.cher.client.test;
+
+import com.threerings.cocktail.cher.Log;
+import com.threerings.cocktail.cher.client.InvocationReceiver;
+
+public class TestReceiver extends InvocationReceiver
+{
+ public void handleTestNotification (int one, String two)
+ {
+ Log.info("Received tell notification [one=" + one +
+ ", two=" + two + "].");
+ }
+}
diff --git a/tests/src/java/com/threerings/presents/server/TestProvider.java b/tests/src/java/com/threerings/presents/server/TestProvider.java
index d8d3bbb44..a3141b168 100644
--- a/tests/src/java/com/threerings/presents/server/TestProvider.java
+++ b/tests/src/java/com/threerings/presents/server/TestProvider.java
@@ -1,9 +1,12 @@
//
-// $Id: TestProvider.java,v 1.3 2001/07/19 18:08:20 mdb Exp $
+// $Id: TestProvider.java,v 1.4 2001/07/19 19:18:07 mdb Exp $
package com.threerings.cocktail.cher.server.test;
import com.threerings.cocktail.cher.Log;
+import com.threerings.cocktail.cher.client.test.TestService;
+import com.threerings.cocktail.cher.data.ClientObject;
+import com.threerings.cocktail.cher.server.CherServer;
import com.threerings.cocktail.cher.server.InvocationProvider;
/**
@@ -11,9 +14,17 @@ import com.threerings.cocktail.cher.server.InvocationProvider;
*/
public class TestProvider extends InvocationProvider
{
- public Object[] handleTestRequest (String one, int two)
+ public Object[] handleTestRequest (
+ ClientObject source, String one, int two)
{
Log.info("Test request [one=" + one + ", two=" + two + "].");
+
+ // issue a test notification just for kicks
+ Object[] args = new Object[] { new Integer(1), "two" };
+ CherServer.invmgr.sendNotification(
+ source.getOid(), TestService.MODULE, "Test", args);
+
+ // and issue a response to this invocation request
return createResponse("TestSucceeded", one, new Integer(two));
}
}