Beginnings of invocation services implementation.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@67 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Client.java,v 1.6 2001/06/11 17:44:03 mdb Exp $
|
||||
// $Id: Client.java,v 1.7 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.client;
|
||||
|
||||
@@ -141,6 +141,31 @@ public class Client
|
||||
return (_comm != null) ? _comm.getDObjectManager() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Every client has an associated <code>ClientObject</code> instance.
|
||||
*
|
||||
* @return the oid of the client object or -1 if we are not currently
|
||||
* connected to the server.
|
||||
*/
|
||||
public int getClientOid ()
|
||||
{
|
||||
return (_comm != null) ? _comm.getClientOid() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the invocation manager associated with this session. This
|
||||
* reference is only valid for the duration of the session and a new
|
||||
* reference must be obtained if the client disconnects and reconnects
|
||||
* to the server.
|
||||
*
|
||||
* @return the invocation manager in effect or null if we have no
|
||||
* established connection to the server.
|
||||
*/
|
||||
public InvocationManager getInvocationManager ()
|
||||
{
|
||||
return (_comm != null) ? _comm.getInvocationManager() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests that this client connect and logon to the server with
|
||||
* which it was previously configured.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: Communicator.java,v 1.7 2001/06/09 23:39:03 mdb Exp $
|
||||
// $Id: Communicator.java,v 1.8 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.client;
|
||||
|
||||
@@ -54,14 +54,31 @@ public class Communicator
|
||||
/**
|
||||
* Returns the distributed object manager in effect for this session.
|
||||
* This instance is only valid while the client is connected to the
|
||||
* server. If we become disconnected and have to reconnect, a new omgr
|
||||
* instance should be obtained.
|
||||
* server.
|
||||
*/
|
||||
public DObjectManager getDObjectManager ()
|
||||
{
|
||||
return _omgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the oid of the client object associated with this session.
|
||||
* It is only valid for the duration of the session.
|
||||
*/
|
||||
public int getClientOid ()
|
||||
{
|
||||
return _cloid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the invocation manager associated with this session. This
|
||||
* reference is only valid for the duration of the session.
|
||||
*/
|
||||
public InvocationManager getInvocationManager ()
|
||||
{
|
||||
return _invmgr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs on to the server and initiates our full-duplex message
|
||||
* exchange.
|
||||
@@ -155,10 +172,14 @@ public class Communicator
|
||||
Log.info("Logon succeeded: " + data);
|
||||
|
||||
// extract bootstrap information
|
||||
_cloid = data.clientOid;
|
||||
|
||||
// create our distributed object manager
|
||||
_omgr = new ClientDObjectMgr(this, _client);
|
||||
|
||||
// create our invocation manager
|
||||
_invmgr = new InvocationManager(_client, data.invOid);
|
||||
|
||||
// create a new writer thread and start it up
|
||||
if (_writer != null) {
|
||||
throw new RuntimeException("Writer already started!?");
|
||||
@@ -481,4 +502,6 @@ public class Communicator
|
||||
protected DataInputStream _din;
|
||||
|
||||
protected ClientDObjectMgr _omgr;
|
||||
protected int _cloid;
|
||||
protected InvocationManager _invmgr;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
//
|
||||
// $Id: InvocationDirector.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.client;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
|
||||
import com.threerings.cocktail.cher.Log;
|
||||
import com.threerings.cocktail.cher.data.*;
|
||||
import com.threerings.cocktail.cher.dobj.*;
|
||||
import com.threerings.cocktail.cher.util.IntMap;
|
||||
|
||||
/**
|
||||
* 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 manager 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 InvocationManager
|
||||
implements Subscriber
|
||||
{
|
||||
/**
|
||||
* Constructs a new invocation manager 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 InvocationManager (Client client, int imoid)
|
||||
{
|
||||
_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 derived from the invocation
|
||||
* name. For example, if the caller invoked a procedure named
|
||||
* <code>SwitchLocation</code>, the response would be delivered via a
|
||||
* call to the <code>handleSwitchLocationResponse</code> method on the
|
||||
* response target object. The signature of that method would be
|
||||
* defined by the arguments provided in the response message.
|
||||
*
|
||||
* @param name the name of the invocation.
|
||||
* @param args the arguments of the invocation.
|
||||
* @param rsptarget the object that will receive the response, or null
|
||||
* if no response is desired.
|
||||
*/
|
||||
public void invoke (String name, Object[] args, Object rsptarget)
|
||||
{
|
||||
int invid = nextInvocationId();
|
||||
|
||||
// we need an args array for a message that can contain the
|
||||
// invocation name, an invocation id and the invocation arguments
|
||||
Object[] iargs = new Object[args.length+2];
|
||||
System.arraycopy(args, 0, iargs, 2, args.length);
|
||||
iargs[0] = name;
|
||||
iargs[1] = new Integer(invid);
|
||||
|
||||
// create a message event on the invocation manager object
|
||||
MessageEvent event = new MessageEvent(
|
||||
_imoid, InvocationObject.MESSAGE_NAME, iargs);
|
||||
|
||||
// if we have a response target, register that for later receipt
|
||||
// of the response
|
||||
if (rsptarget != null) {
|
||||
_targets.put(invid, new Response(name, rsptarget));
|
||||
}
|
||||
|
||||
// and finally ship off the invocation message
|
||||
_omgr.postEvent(event);
|
||||
}
|
||||
|
||||
public void objectAvailable (DObject object)
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
public void requestFailed (int oid, ObjectAccessException cause)
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
/**
|
||||
* Process incoming message requests on user object.
|
||||
*/
|
||||
public boolean handleEvent (DEvent event, DObject target)
|
||||
{
|
||||
// we only care about message events
|
||||
if (!(event instanceof MessageEvent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MessageEvent mevt = (MessageEvent)event;
|
||||
|
||||
// and only those of proper name
|
||||
if (!mevt.getName().equals(InvocationObject.MESSAGE_NAME)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// we've got an invocation response, so we extract the args and
|
||||
// process it
|
||||
Object[] args = mevt.getArgs();
|
||||
int invid = ((Integer)args[0]).intValue();
|
||||
String name = (String)args[1];
|
||||
|
||||
Response rsp = (Response)_targets.get(invid);
|
||||
if (rsp == null) {
|
||||
Log.warning("No target for invocation response " +
|
||||
"[rsp=" + mevt + "].");
|
||||
return true;
|
||||
}
|
||||
|
||||
// prune the method arguments from the full message arguments
|
||||
Object[] rargs = new Object[args.length-2];
|
||||
System.arraycopy(args, 2, rargs, 0, rargs.length);
|
||||
|
||||
// and invoke the response method
|
||||
Method rspmeth = getResponseMethod(name, target, rargs);
|
||||
if (rspmeth != null) {
|
||||
try {
|
||||
rspmeth.invoke(target, rargs);
|
||||
} catch (Exception e) {
|
||||
Log.warning("Error invoking response target method " +
|
||||
"[target=" + target + ", method=" + rspmeth +
|
||||
", error=" + e + "].");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Method getResponseMethod (String name, Object target,
|
||||
Object[] args)
|
||||
{
|
||||
Class tclass = target.getClass();
|
||||
String key = tclass.getName() + ":" + name;
|
||||
Method method = (Method)_methcache.get(key);
|
||||
|
||||
if (method == null) {
|
||||
String methname = "handle" + name;
|
||||
method = findMethod(tclass, name);
|
||||
if (method == null) {
|
||||
Log.warning("Unable to locate method on response target " +
|
||||
"[rtclass=" + tclass.getName() +
|
||||
", method=" + methname + "].");
|
||||
return null;
|
||||
}
|
||||
_methcache.put(key, method);
|
||||
}
|
||||
|
||||
return method;
|
||||
}
|
||||
|
||||
protected static Method findMethod (Class clazz, String name)
|
||||
{
|
||||
Method[] methods = clazz.getMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
if (methods[i].getName().equals(name)) {
|
||||
return methods[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected 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 ClientObject _clobj;
|
||||
|
||||
protected int _invocationId;
|
||||
protected IntMap _targets = new IntMap();
|
||||
protected HashMap _methcache = new HashMap();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// $Id: ClientObject.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.data;
|
||||
|
||||
import com.threerings.cocktail.cher.dobj.DObject;
|
||||
|
||||
/**
|
||||
* Every client in the system has an associated client object to which
|
||||
* only they subscribe. The client object can be used to deliver messages
|
||||
* solely to a particular client as well as to publish client-specific
|
||||
* data.
|
||||
*/
|
||||
public class ClientObject extends DObject
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// $Id: InvocationObject.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.data;
|
||||
|
||||
import com.threerings.cocktail.cher.dobj.DObject;
|
||||
|
||||
/**
|
||||
* A single invocation object is created by the server invocation manager
|
||||
* and is used to receive invocation request messages from the client. The
|
||||
* server presently delivers invocation messages to the client via the
|
||||
* client object.
|
||||
*/
|
||||
public class InvocationObject extends DObject
|
||||
{
|
||||
/**
|
||||
* This constant is used to identify messages on both ends of the
|
||||
* invocation services.
|
||||
*/
|
||||
public static final String MESSAGE_NAME = "invoke";
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: DObject.java,v 1.11 2001/06/13 05:17:55 mdb Exp $
|
||||
// $Id: DObject.java,v 1.12 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.dobj;
|
||||
|
||||
@@ -235,6 +235,19 @@ public class DObject
|
||||
_oid = oid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by derived instances when an attribute setter method was
|
||||
* called.
|
||||
*/
|
||||
protected void requestAttributeChange (String name, Object value)
|
||||
{
|
||||
// generate an attribute changed event
|
||||
AttributeChangedEvent event = new AttributeChangedEvent(
|
||||
_oid, name, value);
|
||||
// and dispatch it to our dobjmgr
|
||||
_mgr.postEvent(event);
|
||||
}
|
||||
|
||||
protected int _oid;
|
||||
protected DObjectManager _mgr;
|
||||
protected ArrayList _subscribers = new ArrayList();
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
//
|
||||
// $Id: MessageEvent.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.dobj;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import com.threerings.cocktail.cher.dobj.io.ValueMarshaller;
|
||||
|
||||
/**
|
||||
* A message event is used to dispatch a message to all subscribers of a
|
||||
* distributed object without actually changing any of the fields of the
|
||||
* object. A message has a name, by which different subscribers of the
|
||||
* same object can distinguish their different messages, and an array of
|
||||
* arguments by which any contents of the message can be delivered.
|
||||
*
|
||||
* @see DObjectManager.postEvent
|
||||
*/
|
||||
public class MessageEvent extends TypedEvent
|
||||
{
|
||||
/** The typed object code for this event. */
|
||||
public static final short TYPE = TYPE_BASE + 3;
|
||||
|
||||
/**
|
||||
* Constructs a new message event on the specified target object with
|
||||
* the supplied name and arguments.
|
||||
*
|
||||
* @param targetOid the object id of the object whose attribute has
|
||||
* changed.
|
||||
* @param name the name of the message event.
|
||||
* @param args the arguments for this message. This array should
|
||||
* contain only values of valid distributed object types.
|
||||
*/
|
||||
public MessageEvent (int targetOid, String name, Object[] args)
|
||||
{
|
||||
super(targetOid);
|
||||
_name = name;
|
||||
_args = args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a blank instance of this event in preparation for
|
||||
* unserialization from the network.
|
||||
*/
|
||||
public MessageEvent ()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the message.
|
||||
*/
|
||||
public String getName ()
|
||||
{
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the arguments to this message.
|
||||
*/
|
||||
public Object[] getArgs ()
|
||||
{
|
||||
return _args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies this attribute change to the object.
|
||||
*/
|
||||
public boolean applyToObject (DObject target)
|
||||
throws ObjectAccessException
|
||||
{
|
||||
// nothing to do here
|
||||
return true;
|
||||
}
|
||||
|
||||
public short getType ()
|
||||
{
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public void writeTo (DataOutputStream out)
|
||||
throws IOException
|
||||
{
|
||||
super.writeTo(out);
|
||||
out.writeUTF(_name);
|
||||
if (_args != null) {
|
||||
out.writeInt(_args.length);
|
||||
for (int i = 0; i < _args.length; i++) {
|
||||
ValueMarshaller.writeTo(out, _args[i]);
|
||||
}
|
||||
} else {
|
||||
out.writeInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void readFrom (DataInputStream in)
|
||||
throws IOException
|
||||
{
|
||||
super.readFrom(in);
|
||||
_name = in.readUTF();
|
||||
int args = in.readInt();
|
||||
if (args > 0) {
|
||||
_args = new Object[args];
|
||||
for (int i = 0; i < args; i++) {
|
||||
_args[i] = ValueMarshaller.readFrom(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String toString ()
|
||||
{
|
||||
return "[MSG:targetOid=" + _toid + ", name=" + _name +
|
||||
", args=" + _args + "]";
|
||||
}
|
||||
|
||||
protected String _name;
|
||||
protected Object[] _args;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// $Id: AuthResponseData.java,v 1.4 2001/05/30 23:58:31 mdb Exp $
|
||||
// $Id: AuthResponseData.java,v 1.5 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.net;
|
||||
|
||||
@@ -23,6 +23,12 @@ public class AuthResponseData extends DObject
|
||||
*/
|
||||
public String code;
|
||||
|
||||
/** The oid of this client's associated distributed object. */
|
||||
public int clientOid;
|
||||
|
||||
/** The oid to which to send invocation requests. */
|
||||
public int invOid;
|
||||
|
||||
public String toString ()
|
||||
{
|
||||
return "[code=" + code + "]";
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// $Id: InvocationManager.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
|
||||
|
||||
package com.threerings.cocktail.cher.server;
|
||||
|
||||
import com.threerings.cocktail.cher.Log;
|
||||
import com.threerings.cocktail.cher.dobj.*;
|
||||
|
||||
/**
|
||||
* 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 server invocation manager listens for invocation requests from
|
||||
* the client and passes them on to the invocation provider registered for
|
||||
* the requested invocation type. It also provides a mechanism by which
|
||||
* responses and asynchronous notification invocations can be delivered to
|
||||
* the client.
|
||||
*/
|
||||
public class InvocationManager
|
||||
implements Subscriber
|
||||
{
|
||||
public InvocationManager (DObjectManager omgr)
|
||||
{
|
||||
_omgr = omgr;
|
||||
|
||||
// create the object on which we'll listen for invocation requests
|
||||
omgr.createObject(DObject.class, this, true);
|
||||
}
|
||||
|
||||
public int getOid ()
|
||||
{
|
||||
return _invoid;
|
||||
}
|
||||
|
||||
public void objectAvailable (DObject object)
|
||||
{
|
||||
// this must be our invocation object
|
||||
_invoid = object.getOid();
|
||||
}
|
||||
|
||||
public void requestFailed (int oid, ObjectAccessException cause)
|
||||
{
|
||||
// if for some reason we were unable to create our invocation
|
||||
// object, we'll end up here
|
||||
Log.warning("Unable to create invocation object " +
|
||||
"[reason=" + cause + "].");
|
||||
_invoid = -1;
|
||||
}
|
||||
|
||||
public boolean handleEvent (DEvent event, DObject target)
|
||||
{
|
||||
// we shouldn't be getting non-message events, but check just to
|
||||
// be sure
|
||||
if (!(event instanceof MessageEvent)) {
|
||||
Log.warning("Got non-message event!? [evt=" + event + "].");
|
||||
return true;
|
||||
}
|
||||
|
||||
MessageEvent mevt = (MessageEvent)event;
|
||||
// make sure the name is proper just for sanities sake
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected DObjectManager _omgr;
|
||||
protected int _invoid;
|
||||
}
|
||||
Reference in New Issue
Block a user