The great invocation services rethink of 2002! Rearchitected the remote

method invocation services and converted everything to the new style.
Could this be my biggest checkin ever?


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1642 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2002-08-14 19:08:01 +00:00
parent 4481c5f835
commit e54a4d41f4
161 changed files with 6083 additions and 2805 deletions
@@ -0,0 +1,56 @@
//
// $Id: BasicDirector.java,v 1.1 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
import com.threerings.presents.util.PresentsContext;
/**
* Handles functionality common to nearly all client directors. They
* generally need to be session observers so that they can set themselves
* up when the client logs on (by overriding {@link #clientDidLogon}) and
* clean up after themselves when the client logs off (by overriding
* {@link #clientDidLogoff}).
*/
public class BasicDirector
implements SessionObserver
{
/**
* Derived directors will need to provide the basic director with a
* context that it can use to register itself with the necessary
* entities.
*/
protected BasicDirector (PresentsContext ctx)
{
// listen for session start and end
Client client = ctx.getClient();
client.addClientObserver(this);
// if we're already logged on, fire off a call to fetch services
if (client.isLoggedOn()) {
fetchServices(client);
}
}
// documentation inherited from interface
public void clientDidLogon (Client client)
{
fetchServices(client);
}
// documentation inherited from interface
public void clientDidLogoff (Client client)
{
}
/**
* Derived directors can override this method and obtain any services
* they'll need during their operation via calls to {@link
* Client#getService}. It will automatically be called when the client
* logs on or when the director is constructed if it is constructed
* after the client is already logged on.
*/
protected void fetchServices (Client client)
{
}
}
@@ -1,5 +1,5 @@
//
// $Id: Client.java,v 1.27 2002/06/04 16:34:24 mdb Exp $
// $Id: Client.java,v 1.28 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
@@ -196,6 +196,40 @@ public class Client
return _invdir;
}
/**
* Returns the first bootstrap service that could be located that
* implements the supplied {@link InvocationService} derivation.
* <code>null</code> is returned if no such service could be found.
*/
public InvocationService getService (Class sclass)
{
int scount = _bstrap.services.size();
for (int ii = 0; ii < scount; ii++) {
InvocationService service = (InvocationService)
_bstrap.services.get(ii);
if (sclass.isInstance(service)) {
return service;
}
}
return null;
}
/**
* Like {@link #getService} except that a {@link RuntimeException} is
* thrown if the service is not available. Useful to avoid redundant
* error checking when you know that the shit will hit the fan if a
* particular invocation service is not available.
*/
public InvocationService requireService (Class sclass)
{
InvocationService isvc = getService(sclass);
if (isvc == null) {
throw new RuntimeException(sclass.getName() + " isn't available. " +
"I can't bear to go on.");
}
return isvc;
}
/**
* Returns a reference to the bootstrap data provided to this client
* at logon time.
@@ -219,7 +253,7 @@ public class Client
/**
* Returns true if we are logged on, false if we're not.
*/
public synchronized boolean loggedOn ()
public synchronized boolean isLoggedOn ()
{
// if we have a communicator, we're logged on
return (_comm != null);
@@ -312,7 +346,7 @@ public class Client
notifyObservers(Client.CLIENT_FAILED_TO_LOGON, cause);
}
};
_invdir.init(_comm.getDObjectManager(), _cloid, _bstrap.invOid, rl);
_invdir.init(_comm.getDObjectManager(), _cloid, rl);
// we can't quite call initialization completed at this point
// because we need for the invocation director to fully initialize
@@ -356,6 +390,8 @@ public class Client
public void run () {
// clear out our communicator reference
_comm = null;
// and let our invocation director know we're logged off
_invdir.cleanup();
}
});
}
@@ -0,0 +1,34 @@
//
// $Id: InvocationDecoder.java,v 1.1 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
import com.samskivert.util.StringUtil;
import com.threerings.presents.Log;
/**
* Provides the basic functionality used to dispatch invocation
* notification events.
*/
public abstract class InvocationDecoder
{
/** The receiver for which we're decoding and dipatching
* notifications. */
public InvocationReceiver receiver;
/**
* Returns the generated hash code that is used to identify this
* invocation notification service.
*/
public abstract String getReceiverCode ();
/**
* Dispatches the specified method to our receiver.
*/
public void dispatchNotification (int methodId, Object[] args)
{
Log.warning("Requested to dispatch unknown method " +
"[receiver=" + receiver + ", methodId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
}
}
@@ -1,70 +1,69 @@
//
// $Id: InvocationDirector.java,v 1.20 2002/04/16 21:37:23 mdb Exp $
// $Id: InvocationDirector.java,v 1.21 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Iterator;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.samskivert.util.ResultListener;
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;
import com.threerings.presents.client.InvocationReceiver.Registration;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller.ListenerMarshaller;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.EventListener;
import com.threerings.presents.dobj.InvocationNotificationEvent;
import com.threerings.presents.dobj.InvocationRequestEvent;
import com.threerings.presents.dobj.InvocationResponseEvent;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.Subscriber;
/**
* 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.
* Handles the client side management of the invocation services.
*/
public class InvocationDirector
implements MessageListener
implements EventListener
{
/**
* Initializes the invocation director.
* Initializes the invocation director. This is called when the client
* establishes a connection with the server.
*
* @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,
public void init (DObjectManager omgr, final int cloid,
final ResultListener initListener)
{
// keep this for later
_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)
{
// keep a handle on this bad boy
_clobj = (ClientObject)object;
// add ourselves as a message listener
object.addListener(InvocationDirector.this);
_clobj.addListener(InvocationDirector.this);
// assign a mapping to already registered receivers
assignReceiverIds();
// let the client know that we're ready to go now that
// we've got our subscription to the client object
@@ -83,224 +82,243 @@ public class InvocationDirector
}
/**
* 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.
* Clears out our session information. This is called when the client
* ends its session with the server.
*/
public int invoke (String module, String procedure, Object[] args,
Object rsptarget)
public void cleanup ()
{
int invid = nextInvocationId();
// wipe our client object, receiver mappings and listener mappings
_clobj = null;
_receivers.clear();
_listeners.clear();
// if null arguments were supplied, assume zero arguments
if (args == null) {
args = new Object[0];
// also reset our counters
_requestId = 0;
_receiverId = 0;
}
/**
* Registers an invocation notification receiver by way of its
* notification event decoder.
*/
public void registerReceiver (InvocationDecoder decoder)
{
// add the receiver to the list
_reclist.add(decoder);
// if we're already online, assign a receiver id to this decoder
if (_clobj != null) {
assignReceiverId(decoder);
}
}
/**
* Removes a receiver registration.
*/
public void unregisterReceiver (String receiverCode)
{
// remove the receiver from the list
for (Iterator iter = _reclist.iterator(); iter.hasNext(); ) {
InvocationDecoder decoder = (InvocationDecoder)iter.next();
if (decoder.getReceiverCode().equals(receiverCode)) {
iter.remove();
}
}
// 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);
// if we're logged on, clear out any receiver id mapping
if (_clobj != null) {
_clobj.removeFromReceivers(receiverCode);
}
}
/**
* Assigns a receiver id to this decoder and publishes it in the
* {@link ClientObject#receivers} field.
*/
protected void assignReceiverId (InvocationDecoder decoder)
{
Registration reg = new Registration(
decoder.getReceiverCode(), nextReceiverId());
// stick the mapping into the client object
_clobj.addToReceivers(reg);
// and map the receiver in our receivers table
_receivers.put(reg.receiverId, decoder);
}
/**
* Called when we log on; generates mappings for all receivers
* registered prior to logon.
*/
protected void assignReceiverIds ()
{
// pack all the set add events into a single transaction
_clobj.startTransaction();
try {
for (Iterator iter = _reclist.iterator(); iter.hasNext(); ) {
assignReceiverId((InvocationDecoder)iter.next());
}
} finally {
_clobj.commitTransaction();
}
}
/**
* Requests that the specified invocation request be packaged up and
* sent to the supplied invocation oid.
*/
public void sendRequest (
int invOid, int invCode, int methodId, Object[] args)
{
// configure any invocation listener marshallers among the
// arguments
int acount = args.length;
for (int ii = 0; ii < acount; ii++) {
Object arg = args[ii];
if (arg instanceof ListenerMarshaller) {
ListenerMarshaller lm = (ListenerMarshaller)arg;
lm.callerOid = _clobj.getOid();
lm.requestId = nextRequestId();
// create a mapping for this marshaller so that we can
// properly dispatch responses sent to it
_listeners.put(lm.requestId, lm);
}
}
// create an invocation request event
InvocationRequestEvent event =
new InvocationRequestEvent(invOid, invCode, methodId, args);
// 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);
event.setSourceOid(_clobj.getOid());
// and finally ship off the invocation message
// Log.info("Sending invocation request " + event + ".");
// now dispatch the event
_omgr.postEvent(event);
return invid;
}
/**
* Registers the supplied invocation receiver instance as the handler
* for all invocation notifications for the specified module.
* Process notification and response events arriving on user object.
*/
public void registerReceiver (String module, InvocationReceiver receiver)
public void eventReceived (DEvent event)
{
if (_receivers == null) {
_receivers = new HashMap();
}
_receivers.put(module, receiver);
}
if (event instanceof InvocationResponseEvent) {
InvocationResponseEvent ire = (InvocationResponseEvent)event;
handleInvocationResponse(
ire.getRequestId(), ire.getMethodId(), ire.getArgs());
/**
* 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);
} else if (event instanceof InvocationNotificationEvent) {
InvocationNotificationEvent ine =
(InvocationNotificationEvent)event;
handleInvocationNotification(
ine.getReceiverId(), ine.getMethodId(), ine.getArgs());
}
}
/**
* Process incoming message requests on user object.
* Dispatches an invocation response.
*/
public void messageReceived (MessageEvent event)
protected void handleInvocationResponse (
int reqId, int methodId, Object[] args)
{
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) + "].");
// look up the invocation marshaller registered for that response
ListenerMarshaller listener = (ListenerMarshaller)
_listeners.remove(reqId);
if (listener == null) {
Log.warning("Received invocation response for which we have " +
"no registered listener [reqId=" + reqId +
", methId=" + methodId +
", 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);
// Log.info("Dispatching invocation response " +
// "[listener=" + listener + ", methId=" + methodId +
// ", args=" + StringUtil.toString(args) + "].");
// 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
// dispatch the response
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);
listener.dispatchResponse(methodId, args);
} catch (Throwable t) {
Log.warning("Invocation response listener choked " +
"[listener=" + listener + ", methId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
Log.logStackTrace(t);
}
}
/**
* Processes an invocation notification message.
* Dispatches an invocation notification.
*/
protected void handleInvocationNotification (Object[] args)
protected void handleInvocationNotification (
int receiverId, int methodId, 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) + "].");
// look up the decoder registered for this receiver
InvocationDecoder decoder = (InvocationDecoder)
_receivers.get(receiverId);
if (decoder == null) {
Log.warning("Received notification for which we have no " +
"registered receiver [recvId=" + receiverId +
", methodId=" + methodId +
", 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);
// Log.info("Dispatching invocation notification " +
// "[receiver=" + decoder.receiver + ", methodId=" + methodId +
// ", args=" + StringUtil.toString(args) + "].");
// 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);
decoder.dispatchNotification(methodId, args);
} catch (Throwable t) {
Log.warning("Invocation notification receiver choked " +
"[receiver=" + decoder.receiver +
", methId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
Log.logStackTrace(t);
}
}
public synchronized int nextInvocationId ()
/**
* Used to generate monotonically increasing invocation request ids.
*/
protected synchronized short nextRequestId ()
{
return _invocationId++;
return _requestId++;
}
protected static class Response
/**
* Used to generate monotonically increasing invocation receiver ids.
*/
protected synchronized short nextReceiverId ()
{
public String name;
public Object target;
public Response (String name, Object target)
{
this.name = name;
this.target = target;
}
return _receiverId++;
}
/** The distributed object manager with which we interact. */
protected DObjectManager _omgr;
protected int _imoid;
protected int _cloid;
protected int _invocationId;
protected HashIntMap _targets = new HashIntMap();
protected HashMap _receivers = new HashMap();
/** Our client object; invocation responses and notifications are
* received on this object. */
protected ClientObject _clobj;
/** Used to generate monotonically increasing request ids. */
protected short _requestId;
/** Used to generate monotonically increasing receiver ids. */
protected short _receiverId;
/** Used to keep track of invocation service listeners which will
* receive responses from invocation service requests. */
protected HashIntMap _listeners = new HashIntMap();
/** Used to keep track of invocation notification receivers. */
protected HashIntMap _receivers = new HashIntMap();
/** All registered receivers are maintained in a list so that we can
* assign receiver ids to them when we go online. */
protected ArrayList _reclist = new ArrayList();
}
@@ -1,36 +1,70 @@
//
// $Id: InvocationReceiver.java,v 1.4 2001/10/11 04:07:52 mdb Exp $
// $Id: InvocationReceiver.java,v 1.5 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
import com.threerings.presents.dobj.DSet;
/**
* Classes registered to process invocation notifications should implement
* the invocation receiver interface and register themselves with the
* invocation director. Because the invocation notification procedures are
* looked up using reflection, there are no methods to implement in the
* receiver interface, but it serves as a useful point for documentation
* and as a useful indicator that the class in question is serving as an
* invocation receiver.
* Invocation notification receipt interfaces should be defined as
* extending this interface. Actual notification receivers will implement
* the requisite receiver interface definition and register themselves
* with the {@link InvocationDirector} using the generated {@link
* InvocationDispatcher} class specific to the notification receiver
* interface in question. For example:
*
* <p> 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 director 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.
*
* <p> The name construction is as follows: a notification message
* requesting the invocation of a procedure named <code>Tell</code> will
* result in a method named <code>handleTellNotification</code> 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).
* <pre>
* public class FooDirector implements FooReceiver
* {
* public FooDirector (PresentsContext ctx)
* {
* InvocationDirector idir = ctx.getClient().getInvocationDirector();
* idir.registerReceiver(new FooDispatcher(this));
* }
* }
* </pre>
*
* @see InvocationDirector#registerReceiver
*/
public interface InvocationReceiver
{
/**
* Used to maintain a registry of invocation receivers that can be
* used to convert (large) hash codes into (small) registration
* numbers.
*/
public static class Registration implements DSet.Entry
{
/** The unique hash code associated with this invocation receiver
* class. */
public String receiverCode;
/** The unique id assigned to this invocation receiver class at
* registration time. */
public short receiverId;
/** Creates and initializes a registration instance. */
public Registration (String receiverCode, short receiverId)
{
this.receiverCode = receiverCode;
this.receiverId = receiverId;
}
/** Creates a blank instance suitable for unserialization. */
public Registration ()
{
}
// documentation inherited from interface
public Comparable getKey ()
{
return receiverCode;
}
/** Generates a string representation of this instance. */
public String toString ()
{
return "[" + receiverCode + " => " + receiverId + "]";
}
}
}
@@ -0,0 +1,86 @@
//
// $Id: InvocationService.java,v 1.1 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
/**
* Serves as the base interface for invocation services. An invocation
* service can be defined by extending this interface and defining service
* methods, as well as response listeners (which must extend {@link
* InvocationListener}). For example:
*
* <pre>
* public interface LocationService extends InvocationService
* {
*
* // Used to communicate responses to moveTo() requests.
* public interface MoveListener extends InvocationListener
* {
* // Called in response to a successful moveTo() request.
* public void moveSucceeded (PlaceConfig config);
* }
*
* // Requests that this client's body be moved to the specified
* // location.
* //
* // @param placeId the object id of the place object to which the
* // body should be moved.
* // @param listener the listener that will be informed of success or
* // failure.
* public void moveTo (int placeId, MoveListener listener);
* }
* </pre>
*
* From this interface, a <code>LocationProvider</code> interface will be
* generated which should be implemented by whatever server entity that
* will actually provide the server side of this invocation service. That
* provider interface would look like the following:
*
* <pre>
* public interface LocationProvider extends InvocationProvider
* {
* // Requests that this client's body be moved to the specified
* // location.
* //
* // @param caller the client object of the client that invoked this
* // remotely callable method.
* // @param placeId the object id of the place object to which the
* // body should be moved.
* // @param listener the listener that should be informed of success
* // or failure.
* public void moveTo (ClientObject caller, int placeId,
* MoveListener listener)
* throws InvocationException;
* }
* </pre>
*/
public interface InvocationService
{
/**
* Invocation service methods that require a response should take a
* listener argument that can be notified of request success or
* failure. The listener argument should extend this interface so that
* generic failure can be reported in all cases. For example:
*
* <pre>
* // Used to communicate responses to <code>moveTo</code> requests.
* public interface MoveListener extends InvocationListener
* {
* // Called in response to a successful <code>moveTo</code>
* // request.
* public void moveSucceeded (PlaceConfig config);
* }
* </pre>
*/
public static interface InvocationListener
{
/**
* Called to report request failure. If the invocation services
* system detects failure of any kind, it will report it via this
* callback. Particular services may also make use of this
* callback to report failures of their own, or they may opt to
* define more specific failure callbacks.
*/
public void requestFailed (String cause);
}
}
@@ -0,0 +1,32 @@
//
// $Id: LoggingListener.java,v 1.1 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
import com.samskivert.util.Log;
/**
* Implements the basic {@link InvocationListener} and logs the failure.
*/
public class LoggingListener
implements InvocationService.InvocationListener
{
/**
* Constructs a listener that will report the supplied error message
* along with the reason for failure to the supplied log object.
*/
public LoggingListener (Log log, String errmsg)
{
_log = log;
_errmsg = errmsg;
}
// documentation inherited from interface
public void requestFailed (String reason)
{
_log.warning(_errmsg + " [reason=" + reason + "].");
}
protected Log _log;
protected String _errmsg;
}
@@ -1,36 +1,32 @@
//
// $Id: TimeBaseService.java,v 1.2 2002/05/29 18:44:36 mdb Exp $
// $Id: TimeBaseService.java,v 1.3 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.client;
import com.threerings.presents.Log;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationDirector;
import com.threerings.presents.data.TimeBaseCodes;
import com.threerings.presents.client.InvocationService;
/**
* Provides a means by which to obtain access to a time base object which
* can be used to convert delta times into absolute times.
*/
public class TimeBaseService
implements TimeBaseCodes
public interface TimeBaseService extends InvocationService
{
/**
* Requests the oid of the specified time base object be returned. The
* supplied response target must provide two methods to handle the
* responses generated by this request:
*
* <pre>
* public void handleTimeOidResponse (int invid, int timeOid);
* public void handleGetTimeOidFailed (int invid, String reason);
* </pre>
* Used to communicated the result of a {@link #getTimeOid} request.
*/
public static void getTimeOid (
Client client, String timeBase, Object rsptarget)
public static interface GotTimeBaseListener extends InvocationListener
{
InvocationDirector invdir = client.getInvocationDirector();
invdir.invoke(MODULE_NAME, GET_TIME_OID_REQUEST,
new Object[] { timeBase }, rsptarget);
Log.debug("Sent getTimeOid request [timeBase=" + timeBase + "].");
/**
* Communicates the result of a successful {@link #getTimeOid}
* request.
*/
public void gotTimeOid (int timeOid);
}
/**
* Requests the oid of the specified time base object be fetched.
*/
public void getTimeOid (
Client client, String timeBase, GotTimeBaseListener listener);
}
@@ -0,0 +1,28 @@
//
// $Id: ClientObject.dobj,v 1.1 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.data;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
/**
* 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
{
/** Used to publish all invocation service receivers registered on
* this client. */
public DSet receivers = new DSet();
/**
* Returns a short string identifying this client.
*/
public String who ()
{
return "(" + getOid() + ")";
}
}
@@ -1,9 +1,10 @@
//
// $Id: ClientObject.java,v 1.2 2001/10/11 04:07:52 mdb Exp $
// $Id: ClientObject.java,v 1.3 2002/08/14 19:07:54 mdb Exp $
package com.threerings.presents.data;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DSet;
/**
* Every client in the system has an associated client object to which
@@ -13,4 +14,64 @@ import com.threerings.presents.dobj.DObject;
*/
public class ClientObject extends DObject
{
/** The field name of the <code>receivers</code> field. */
public static final String RECEIVERS = "receivers";
/** Used to publish all invocation service receivers registered on
* this client. */
public DSet receivers = new DSet();
/**
* Returns a short string identifying this client.
*/
public String who ()
{
return "(" + getOid() + ")";
}
/**
* Requests that the specified entry be added to the
* <code>receivers</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void addToReceivers (DSet.Entry elem)
{
requestEntryAdd(RECEIVERS, elem);
}
/**
* Requests that the entry matching the supplied key be removed from
* the <code>receivers</code> set. The set will not change until the
* event is actually propagated through the system.
*/
public void removeFromReceivers (Object key)
{
requestEntryRemove(RECEIVERS, key);
}
/**
* Requests that the specified entry be updated in the
* <code>receivers</code> set. The set will not change until the event is
* actually propagated through the system.
*/
public void updateReceivers (DSet.Entry elem)
{
requestEntryUpdate(RECEIVERS, elem);
}
/**
* Requests that the <code>receivers</code> field be set to the
* specified value. Generally one only adds, updates and removes
* entries of a distributed set, but certain situations call for a
* complete replacement of the set value. The local value will be
* updated immediately and an event will be propagated through the
* system to notify all listeners that the attribute did
* change. Proxied copies of this object (on clients) will apply the
* value change when they received the attribute changed notification.
*/
public void setReceivers (DSet receivers)
{
this.receivers = receivers;
requestAttributeChange(RECEIVERS, receivers);
}
}
@@ -0,0 +1,156 @@
//
// $Id: InvocationMarshaller.java,v 1.1 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.data;
import java.io.IOException;
import com.samskivert.util.StringUtil;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
import com.threerings.presents.Log;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.InvocationResponseEvent;
/**
* Provides a base from which all invocation service marshallers extend.
* Handles functionality common to all marshallers.
*/
public class InvocationMarshaller
implements Streamable, InvocationService
{
/**
* Provides a base from which invocation listener marshallers extend.
*/
public static class ListenerMarshaller
implements Streamable, InvocationListener
{
/** The method id used to dispatch a {@link #requestFailed}
* response. */
public static final int REQUEST_FAILED_RSPID = 0;
/** The oid of the invocation service requester. */
public int callerOid;
/** The request id associated with this listener. */
public short requestId;
/** The actual invocation listener associated with this
* marshalling listener. This is only valid on the client. */
public transient InvocationListener listener;
/** The distributed object manager to use when dispatching proxied
* responses. This is only valid on the server. */
public transient DObjectManager omgr;
// documentation inherited from interface
public void requestFailed (String cause)
{
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, REQUEST_FAILED_RSPID,
new Object[] { cause }));
}
/**
* Called to dispatch an invocation response to our target
* listener.
*/
public void dispatchResponse (int methodId, Object[] args)
{
if (methodId == REQUEST_FAILED_RSPID) {
listener.requestFailed((String)args[0]);
} else {
Log.warning("Requested to dispatch unknown invocation " +
"response [listener=" + listener +
", methodId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
}
}
/**
* Generates a string representation of this instance.
*/
public String toString ()
{
return "[callerOid=" + callerOid + ", reqId=" + requestId +
", type=" + getClass().getName() + "]";
}
}
/**
* Initializes this invocation marshaller instance with the requisite
* information to allow it to operate in the wide world. This is
* called by the invocation manager when an invocation provider is
* registered and should not be called otherwise.
*/
public void init (int invOid, int invCode)
{
_invOid = invOid;
_invCode = invCode;
}
/**
* Sets the invocation oid to which this marshaller should send its
* invocation service requests. This is called by the invocation
* manager in certain initialization circumstances.
*/
public void setInvocationOid (int invOid)
{
_invOid = invOid;
}
/**
* Called by generated invocation marshaller code; packages up and
* sends the specified invocation service request.
*/
protected void sendRequest (Client client, int methodId, Object[] args)
{
client.getInvocationDirector().sendRequest(
_invOid, _invCode, methodId, args);
}
/**
* Writes this instance to the supplied output stream.
*/
public void writeObject (ObjectOutputStream out)
throws IOException
{
out.writeInt(_invOid);
out.writeShort(_invCode);
}
/**
* Reads this instance from the supplied input stream.
*/
public void readObject (ObjectInputStream in)
throws IOException
{
_invOid = in.readInt();
_invCode = in.readShort();
}
/**
* Generates a string representation of this instance.
*/
public String toString ()
{
return "[invOid=" + _invOid + ", code=" + _invCode +
", type=" + getClass().getName() + "]";
}
/** The oid of the invocation object, where invocation service
* requests are sent. */
protected int _invOid;
/** The invocation service code assigned to this service when it was
* registered on the server. */
protected int _invCode;
}
@@ -1,5 +1,5 @@
//
// $Id: InvocationObject.java,v 1.3 2001/10/11 04:07:52 mdb Exp $
// $Id: InvocationObject.java,v 1.4 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.data;
@@ -13,21 +13,4 @@ import com.threerings.presents.dobj.DObject;
*/
public class InvocationObject extends DObject
{
/**
* This constant is used to identify invocation requests sent to the
* server.
*/
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";
}
@@ -1,23 +1,13 @@
//
// $Id: TimeBaseCodes.java,v 1.1 2002/05/28 23:14:06 mdb Exp $
// $Id: TimeBaseCodes.java,v 1.2 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.data;
/**
* Codes and constants relating to the Presents time base services.
*/
public interface TimeBaseCodes
public interface TimeBaseCodes extends InvocationCodes
{
/** The module name for the time services. */
public static final String MODULE_NAME = "time";
/** The message identifier for a request to obtain a particular time
* object. */
public static final String GET_TIME_OID_REQUEST = "GetTimeOid";
/** A response generated for a successful getTimeOid request. */
public static final String TIME_OID_RESPONSE = "TimeOid";
/** An error response generated for GetTimeOid requests. */
public static final String NO_SUCH_TIME_BASE = "m.no_such_time_base";
}
@@ -0,0 +1,67 @@
//
// $Id: TimeBaseMarshaller.java,v 1.1 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.data;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.TimeBaseService;
import com.threerings.presents.client.TimeBaseService.GotTimeBaseListener;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
/**
* Provides the implementation of the {@link TimeBaseService} interface
* that marshalls the arguments and delivers the request to the provider
* on the server. Also provides an implementation of the response listener
* interfaces that marshall the response arguments and deliver them back
* to the requesting client.
*/
public class TimeBaseMarshaller extends InvocationMarshaller
implements TimeBaseService
{
// documentation inherited
public static class GotTimeBaseMarshaller extends ListenerMarshaller
implements GotTimeBaseListener
{
/** The method id used to dispatch {@link #gotTimeOid}
* responses. */
public static final int GOT_TIME_OID = 0;
// documentation inherited from interface
public void gotTimeOid (int arg1)
{
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, GOT_TIME_OID,
new Object[] { new Integer(arg1) }));
}
// documentation inherited
public void dispatchResponse (int methodId, Object[] args)
{
switch (methodId) {
case GOT_TIME_OID:
((GotTimeBaseListener)listener).gotTimeOid(
((Integer)args[0]).intValue());
return;
default:
super.dispatchResponse(methodId, args);
}
}
}
/** The method id used to dispatch {@link #getTimeOid} requests. */
public static final int GET_TIME_OID = 1;
// documentation inherited from interface
public void getTimeOid (Client arg1, String arg2, GotTimeBaseListener arg3)
{
GotTimeBaseMarshaller listener3 = new GotTimeBaseMarshaller();
listener3.listener = arg3;
sendRequest(arg1, GET_TIME_OID, new Object[] {
arg2, listener3
});
}
// Class file generated on 00:26:01 08/11/02.
}
@@ -1,5 +1,5 @@
//
// $Id: DObject.java,v 1.46 2002/07/23 05:52:48 mdb Exp $
// $Id: DObject.java,v 1.47 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.dobj;
@@ -473,6 +473,14 @@ public class DObject implements Streamable
_oid = oid;
}
/**
* Generates a concise string representation of this object.
*/
public String which ()
{
return "[" + getClass().getName() + " " + getOid() + "]";
}
/**
* Generates a string representation of this object.
*/
+82 -109
View File
@@ -1,11 +1,13 @@
//
// $Id: DSet.java,v 1.17 2002/07/23 05:52:48 mdb Exp $
// $Id: DSet.java,v 1.18 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.dobj;
import java.io.IOException;
import java.util.Comparator;
import java.util.Iterator;
import com.samskivert.util.ArrayUtil;
import com.samskivert.util.StringUtil;
import com.threerings.io.ObjectInputStream;
@@ -45,7 +47,7 @@ public class DSet
* its uniqueness in the set. See the {@link DSet} class
* documentation for further information.
*/
public Object getKey ();
public Comparable getKey ();
}
/**
@@ -61,17 +63,8 @@ public class DSet
*/
public DSet (Iterator source)
{
for (int index = 0; source.hasNext(); index++) {
Entry elem = (Entry)source.next();
// expand the array if necessary
if (index >= _entries.length) {
expand(index);
}
// insert the item
_entries[index] = elem;
_size++;
while (source.hasNext()) {
add((Entry)source.next());
}
}
@@ -140,37 +133,16 @@ public class DSet
public Iterator entries ()
{
return new Iterator() {
public boolean hasNext ()
{
// we need to scan to the next entry the first time
if (_index < 0) {
scanToNext();
}
return (_index < _entries.length);
public boolean hasNext () {
return (_index < _size);
}
public Object next ()
{
Object val = _entries[_index];
scanToNext();
return val;
public Object next () {
return _entries[_index++];
}
public void remove ()
{
public void remove () {
throw new UnsupportedOperationException();
}
protected void scanToNext ()
{
for (_index++; _index < _entries.length; _index++) {
if (_entries[_index] != null) {
return;
}
}
}
int _index = -1;
protected int _index = 0;
};
}
@@ -185,32 +157,44 @@ public class DSet
*/
protected boolean add (Entry elem)
{
Object key = elem.getKey();
// determine where we'll be adding the new element
int eidx = ArrayUtil.binarySearch(
_entries, 0, _size, elem, ENTRY_COMP);
// if the element is already in the set, bail now
if (eidx >= 0) {
return false;
}
// convert the index into happy positive land
eidx = (eidx+1)*-1;
// expand our entries array if necessary
int elength = _entries.length;
int index = elength;
// scan the array looking for a slot and/or the entry already in
// the set
for (int i = 0; i < elength; i++) {
Entry el = _entries[i];
// the array may be sparse
if (el == null) {
if (index == elength) {
index = i;
}
} else if (el.getKey().equals(key)) {
return false;
if (_size == elength-1) {
// sanity check
if (elength > 2048) {
Log.warning("Requested to expand to questionably large size " +
"[length=" + elength + "].");
Thread.dumpStack();
}
// create a new array and copy our data into it
Entry[] elems = new Entry[elength*2];
System.arraycopy(_entries, 0, elems, 0, elength);
_entries = elems;
}
// expand the array if necessary
if (index >= _entries.length) {
expand(index);
// if the entry doesn't go at the end, shift the elements down to
// accomodate it
if (eidx < _size) {
System.arraycopy(_entries, eidx, _entries, eidx+1, _size-eidx);
}
// insert the item
_entries[index] = elem;
// stuff the entry into the array and note that we're bigger
_entries[eidx] = elem;
_size++;
return true;
}
@@ -239,17 +223,20 @@ public class DSet
*/
protected boolean removeKey (Object key)
{
// scan the array looking for a matching entry
int elength = _entries.length;
for (int i = 0; i < elength; i++) {
Entry el = _entries[i];
if (el != null && el.getKey().equals(key)) {
_entries[i] = null;
_size--;
return true;
}
// look up this entry's position in our set
int eidx = ArrayUtil.binarySearch(
_entries, 0, _size, key, ENTRY_COMP);
// if we found it, remove it
if (eidx >= 0) {
// shift the remaining elements down
System.arraycopy(_entries, eidx+1, _entries, eidx, _size-eidx-1);
_entries[--_size] = null;
return true;
} else {
return false;
}
return false;
}
/**
@@ -264,19 +251,17 @@ public class DSet
*/
protected boolean update (Entry elem)
{
Object key = elem.getKey();
// look up this entry's position in our set
int eidx = ArrayUtil.binarySearch(
_entries, 0, _size, elem, ENTRY_COMP);
// scan the array looking for a matching entry
int elength = _entries.length;
for (int i = 0; i < elength; i++) {
Entry el = _entries[i];
if (el != null && el.getKey().equals(key)) {
_entries[i] = elem;
return true;
}
// if we found it, update it
if (eidx >= 0) {
_entries[eidx] = elem;
return true;
} else {
return false;
}
return false;
}
/**
@@ -340,35 +325,6 @@ public class DSet
return buf.toString();
}
protected void expand (int index)
{
// sanity check
if (index < 0 || index > Short.MAX_VALUE) {
Log.warning("Requested to expand to accomodate bogus index! " +
"[index=" + index + "].");
Thread.dumpStack();
index = 0;
}
// increase our length in powers of two until we're big enough
int tlength = _entries.length;
while (index >= tlength) {
tlength *= 2;
}
// further sanity checks
if (tlength > 4096) {
Log.warning("Requested to expand to questionably large size " +
"[index=" + index + ", tlength=" + tlength + "].");
Thread.dumpStack();
}
// create a new array and copy our data into it
Entry[] elems = new Entry[tlength];
System.arraycopy(_entries, 0, elems, 0, _entries.length);
_entries = elems;
}
/** The entries of the set (in a sparse array). */
protected Entry[] _entries = new Entry[INITIAL_CAPACITY];
@@ -377,4 +333,21 @@ public class DSet
/** The default capacity of a set instance. */
protected static final int INITIAL_CAPACITY = 2;
/** Used for lookups and to keep the set contents sorted on
* insertions. */
protected static Comparator ENTRY_COMP = new Comparator() {
public int compare (Object o1, Object o2) {
Comparable c1 = (o1 instanceof Entry) ?
((Entry)o1).getKey() : (Comparable)o1;
Comparable c2 = (o2 instanceof Entry) ?
((Entry)o2).getKey() : (Comparable)o2;
return c1.compareTo(c2);
}
public boolean equals (Object obj) {
// we don't care about comparing comparators
return (obj == this);
}
};
}
@@ -0,0 +1,135 @@
//
// $Id: InvocationNotificationEvent.java,v 1.1 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.dobj;
import java.io.IOException;
import com.samskivert.util.StringUtil;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
/**
* Used to dispatch an invocation notification from the server to a
* client.
*
* @see DObjectManager#postEvent
*/
public class InvocationNotificationEvent extends DEvent
{
/**
* Constructs a new invocation notification event on the specified
* target object with the supplied receiver id, method id and
* arguments.
*
* @param targetOid the object id of the object on which the event is
* to be dispatched.
* @param receiverId identifies the receiver to which this notification
* is being dispatched.
* @param methodId the id of the method to be invoked.
* @param args the arguments for the method. This array should contain
* only values of valid distributed object types.
*/
public InvocationNotificationEvent (
int targetOid, short receiverId, int methodId, Object[] args)
{
super(targetOid);
_receiverId = receiverId;
_methodId = (byte)methodId;
_args = args;
}
/**
* Constructs a blank instance of this event in preparation for
* unserialization from the network.
*/
public InvocationNotificationEvent ()
{
}
/**
* Returns the receiver id associated with this notification.
*/
public int getReceiverId ()
{
return _receiverId;
}
/**
* Returns the id of the method associated with this notification.
*/
public int getMethodId ()
{
return _methodId;
}
/**
* Returns the arguments associated with this notification.
*/
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;
}
/**
* Writes our custom streamable fields.
*/
public void writeObject (ObjectOutputStream out)
throws IOException
{
super.writeObject(out);
out.writeShort(_receiverId);
out.writeByte(_methodId);
out.writeObject(_args);
}
/**
* Reads our custom streamable fields.
*/
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
super.readObject(in);
_receiverId = in.readShort();
_methodId = in.readByte();
_args = (Object[])in.readObject();
}
// documentation inherited
protected void notifyListener (Object listener)
{
// nothing to do here
}
// documentation inherited
protected void toString (StringBuffer buf)
{
buf.append("INOT:");
super.toString(buf);
buf.append(", rcvId=").append(_receiverId);
buf.append(", methodId=").append(_methodId);
buf.append(", args=").append(StringUtil.toString(_args));
}
/** Identifies the receiver to which this notification is being
* dispatched. */
protected short _receiverId;
/** The id of the receiver method being invoked. */
protected byte _methodId;
/** The arguments to the receiver method being invoked. */
protected Object[] _args;
}
@@ -0,0 +1,132 @@
//
// $Id: InvocationRequestEvent.java,v 1.1 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.dobj;
import java.io.IOException;
import com.samskivert.util.StringUtil;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
/**
* Used to dispatch an invocation request from the client to the server.
*
* @see DObjectManager#postEvent
*/
public class InvocationRequestEvent extends DEvent
{
/**
* Constructs a new invocation request event on the specified target
* object with the supplied code, method and arguments.
*
* @param targetOid the object id of the object on which the event is
* to be dispatched.
* @param invCode the invocation provider identification code.
* @param methodId the id of the method to be invoked.
* @param args the arguments for the method. This array should contain
* only values of valid distributed object types.
*/
public InvocationRequestEvent (
int targetOid, int invCode, int methodId, Object[] args)
{
super(targetOid);
_invCode = invCode;
_methodId = (byte)methodId;
_args = args;
}
/**
* Constructs a blank instance of this event in preparation for
* unserialization from the network.
*/
public InvocationRequestEvent ()
{
}
/**
* Returns the invocation code associated with this request.
*/
public int getInvCode ()
{
return _invCode;
}
/**
* Returns the id of the method associated with this request.
*/
public int getMethodId ()
{
return _methodId;
}
/**
* Returns the arguments associated with this request.
*/
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;
}
/**
* Writes our custom streamable fields.
*/
public void writeObject (ObjectOutputStream out)
throws IOException
{
super.writeObject(out);
out.writeInt(_invCode);
out.writeByte(_methodId);
out.writeObject(_args);
}
/**
* Reads our custom streamable fields.
*/
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
super.readObject(in);
_invCode = in.readInt();
_methodId = in.readByte();
_args = (Object[])in.readObject();
}
// documentation inherited
protected void notifyListener (Object listener)
{
// nothing to do here
}
// documentation inherited
protected void toString (StringBuffer buf)
{
buf.append("IREQ:");
super.toString(buf);
buf.append(", code=").append(_invCode);
buf.append(", methodId=").append(_methodId);
buf.append(", args=").append(StringUtil.toString(_args));
}
/** The code identifying which invocation provider to which this
* request is directed. */
protected int _invCode;
/** The id of the method being invoked. */
protected byte _methodId;
/** The arguments to the method being invoked. */
protected Object[] _args;
}
@@ -0,0 +1,131 @@
//
// $Id: InvocationResponseEvent.java,v 1.1 2002/08/14 19:07:55 mdb Exp $
package com.threerings.presents.dobj;
import java.io.IOException;
import com.samskivert.util.StringUtil;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
/**
* Used to dispatch an invocation response from the server to the client.
*
* @see DObjectManager#postEvent
*/
public class InvocationResponseEvent extends DEvent
{
/**
* Constructs a new invocation response event on the specified target
* object with the supplied code, method and arguments.
*
* @param targetOid the object id of the object on which the event is
* to be dispatched.
* @param requestId the id of the request to which we are responding.
* @param methodId the method to be invoked.
* @param args the arguments for the method. This array should contain
* only values of valid distributed object types.
*/
public InvocationResponseEvent (
int targetOid, int requestId, int methodId, Object[] args)
{
super(targetOid);
_requestId = (short)requestId;
_methodId = (byte)methodId;
_args = args;
}
/**
* Constructs a blank instance of this event in preparation for
* unserialization from the network.
*/
public InvocationResponseEvent ()
{
}
/**
* Returns the invocation request id associated with this response.
*/
public int getRequestId ()
{
return _requestId;
}
/**
* Returns the method associated with this response.
*/
public int getMethodId ()
{
return _methodId;
}
/**
* Returns the arguments associated with this response.
*/
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;
}
/**
* Writes our custom streamable fields.
*/
public void writeObject (ObjectOutputStream out)
throws IOException
{
super.writeObject(out);
out.writeShort(_requestId);
out.writeByte(_methodId);
out.writeObject(_args);
}
/**
* Reads our custom streamable fields.
*/
public void readObject (ObjectInputStream in)
throws IOException, ClassNotFoundException
{
super.readObject(in);
_requestId = in.readShort();
_methodId = in.readByte();
_args = (Object[])in.readObject();
}
// documentation inherited
protected void notifyListener (Object listener)
{
// nothing to do here
}
// documentation inherited
protected void toString (StringBuffer buf)
{
buf.append("IRSP:");
super.toString(buf);
buf.append(", reqid=").append(_requestId);
buf.append(", methodId=").append(_methodId);
buf.append(", args=").append(StringUtil.toString(_args));
}
/** The id of the request with which this response is associated. */
protected short _requestId;
/** The id of the method being invoked. */
protected byte _methodId;
/** The arguments to the method being invoked. */
protected Object[] _args;
}
@@ -1,9 +1,10 @@
//
// $Id: BootstrapData.java,v 1.6 2002/05/28 22:54:43 mdb Exp $
// $Id: BootstrapData.java,v 1.7 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.net;
import com.threerings.presents.dobj.DObject;
import com.threerings.util.StreamableArrayList;
/**
* A <code>BootstrapData</code> object is communicated back to the client
@@ -16,6 +17,6 @@ public class BootstrapData extends DObject
/** The oid of this client's associated distributed object. */
public int clientOid;
/** The oid to which to send invocation requests. */
public int invOid;
/** A list of handles to invocation services. */
public StreamableArrayList services;
}
@@ -0,0 +1,40 @@
//
// $Id: InvocationDispatcher.java,v 1.1 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
import com.samskivert.util.StringUtil;
import com.threerings.presents.Log;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
/**
* Provides the base class via which invocation service requests are
* dispatched.
*/
public abstract class InvocationDispatcher
{
/** The invocation provider for whom we're dispatching. */
public InvocationProvider provider;
/**
* Creates an instance of the appropriate {@link InvocationMarshaller}
* derived class for use with this dispatcher.
*/
public abstract InvocationMarshaller createMarshaller ();
/**
* Dispatches the specified method to our provider.
*/
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
Log.warning("Requested to dispatch unknown method " +
"[provider=" + provider +
", sourceOid=" + source.getOid() +
", methodId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
}
}
@@ -0,0 +1,19 @@
//
// $Id: InvocationException.java,v 1.1 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
/**
* Used to report failures when executing service requests.
*/
public class InvocationException extends Exception
{
/**
* Constructs an invocation exception with the supplied cause code
* string.
*/
public InvocationException (String cause)
{
super(cause);
}
}
@@ -1,19 +1,30 @@
//
// $Id: InvocationManager.java,v 1.11 2002/04/17 18:20:04 mdb Exp $
// $Id: InvocationManager.java,v 1.12 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.ArrayList;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.threerings.io.Streamable;
import com.threerings.util.StreamableArrayList;
import com.threerings.presents.Log;
import com.threerings.presents.dobj.*;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller.ListenerMarshaller;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.data.InvocationObject;
import com.threerings.presents.util.ClassUtil;
import com.threerings.presents.dobj.DEvent;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.EventListener;
import com.threerings.presents.dobj.InvocationRequestEvent;
import com.threerings.presents.dobj.MessageEvent;
import com.threerings.presents.dobj.ObjectAccessException;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.dobj.Subscriber;
/**
* The invocation services provide client to server invocations (service
@@ -23,11 +34,12 @@ import com.threerings.presents.util.ClassUtil;
* 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).
* and take arguments. All arguments must be {@link Streamable} objects,
* primitive types, or String objects. All arguments are passed by value
* (by serializing and unserializing the arguments); 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
@@ -36,9 +48,19 @@ import com.threerings.presents.util.ClassUtil;
* the client.
*/
public class InvocationManager
implements Subscriber, MessageListener
implements Subscriber, EventListener
{
public InvocationManager (DObjectManager omgr)
/** The list of services that are to be provided to clients at boot
* time. Don't mess with this list! */
public StreamableArrayList bootlist = new StreamableArrayList();
/**
* Constructs an invocation manager which will use the supplied
* distributed object manager to operate its invocation
* services. Generally only one invocation manager should be
* operational in a particular system.
*/
public InvocationManager (RootDObjectManager omgr)
{
_omgr = omgr;
@@ -52,45 +74,42 @@ public class InvocationManager
}
/**
* Registers the supplied invocation provider instance as the handler
* for all invocation requests for the specified module.
*/
public void registerProvider (String module, InvocationProvider provider)
{
_providers.put(module, provider);
}
/**
* Delivers an invocation notification to the specified client. The
* <code>module</code> argument selects which
* <code>InvocationReceiver</code> will be invoked and the
* <code>procedure</code> argument indicates which method will be
* invoked on that receiver.
* Registers the supplied invocation dispatcher, returning a
* marshaller that can be used to send requests to the provider for
* whom the dispatcher is proxying.
*
* <p> The method is constructed as follows: a procedure name of
* <code>Tell</code> will result in a method call to
* <code>handleTellNotification</code>. The arguments provided with
* the notification define the necessary signature of that method,
* according to the argument conversion rules defined by the
* reflection services (<code>Integer</code> is converted to
* <code>int</code>, etc.).
* @param dispatcher the dispatcher to be registered.
* @param bootstrap if true, the service instance will be added to the
* list of invocation service objects provided to the client in the
* bootstrap data.
*/
public void sendNotification (
int cloid, String module, String procedure, Object[] args)
public InvocationMarshaller registerDispatcher (
InvocationDispatcher dispatcher, boolean bootstrap)
{
// 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);
// get the next invocation code
int invCode = nextInvCode();
// create the marshaller and initialize it
InvocationMarshaller marsh = dispatcher.createMarshaller();
marsh.init(_invoid, invCode);
// if we haven't yet finished our own initialization, we need to
// throw this dispatcher on a queue so that we can fill in its
// invocation oid when we know what it should be
if (_invoid == -1) {
_lateInitQueue.add(marsh);
}
// construct a message event and deliver it
MessageEvent nevt = new MessageEvent(
cloid, InvocationObject.NOTIFICATION_NAME, nargs);
PresentsServer.omgr.postEvent(nevt);
// register the dispatcher
_dispatchers.put(invCode, dispatcher);
// if it's a bootstrap service, slap it in the list
if (bootstrap) {
bootlist.add(marsh);
}
// Log.info("Registered service [marsh=" + marsh + "].");
return marsh;
}
public void objectAvailable (DObject object)
@@ -100,6 +119,15 @@ public class InvocationManager
// add ourselves as a message listener
object.addListener(this);
// let any early registered marshallers know about our invoid
while (_lateInitQueue.size() > 0) {
InvocationMarshaller marsh = (InvocationMarshaller)
_lateInitQueue.remove(0);
marsh.setInvocationOid(_invoid);
}
// Log.info("Created invocation service object [oid=" + _invoid + "].");
}
public void requestFailed (int oid, ObjectAccessException cause)
@@ -111,85 +139,118 @@ public class InvocationManager
_invoid = -1;
}
public void messageReceived (MessageEvent event)
// documentation inherited from interface
public void eventReceived (DEvent event)
{
// make sure the name is proper just for sanity's sake
if (!event.getName().equals(InvocationObject.REQUEST_NAME)) {
return;
}
// Log.info("Event received " + event + ".");
// we've got an invocation request, so we process it
Object[] args = event.getArgs();
String module = (String)args[0];
String procedure = (String)args[1];
Integer invid = (Integer)args[2];
// locate a provider for this module
InvocationProvider provider =
(InvocationProvider)_providers.get(module);
if (provider == null) {
Log.warning("No provider registered for invocation request " +
"[evt=" + event + "].");
return;
}
// prune the method arguments from the full message arguments
Object[] margs = new Object[args.length-1];
int cloid = event.getSourceOid();
ClientObject source = (ClientObject)
PresentsServer.omgr.getObject(cloid);
// make sure the client is still around
if (source == null) {
Log.warning("Client no longer around for invocation provider " +
"request [module=" + module +
", proc=" + procedure + ", cloid=" + cloid + "].");
return;
}
margs[0] = source;
System.arraycopy(args, 2, margs, 1, args.length-2);
// look up the method that will handle this procedure
String mname = "handle" + procedure + "Request";
Method procmeth = ClassUtil.getMethod(mname, provider, _methcache);
if (procmeth == null) {
Log.warning("Unable to resolve provider procedure " +
"[provider=" + provider.getClass().getName() +
", method=" + mname + "].");
return;
}
// and invoke it
try {
procmeth.invoke(provider, margs);
} catch (InvocationTargetException ite) {
Throwable te = ite.getTargetException();
if (te instanceof ServiceFailedException) {
// automatically generate a <foo>Failed response
provider.sendResponse(source, invid.intValue(),
procedure + FAILED_SUFFIX,
te.getMessage());
} else {
Log.warning("Invocation procedure failed " +
"[provider=" + provider +
", method=" + procmeth +
", args=" + StringUtil.toString(margs) + "].");
Log.logStackTrace(te);
}
} catch (Exception e) {
Log.warning("Error invoking invocation procedure " +
"[provider=" + provider +
", method=" + procmeth + "].");
Log.logStackTrace(e);
if (event instanceof InvocationRequestEvent) {
InvocationRequestEvent ire = (InvocationRequestEvent)event;
dispatchRequest(ire.getSourceOid(), ire.getInvCode(),
ire.getMethodId(), ire.getArgs());
}
}
protected DObjectManager _omgr;
protected int _invoid;
protected HashMap _providers = new HashMap();
protected HashMap _methcache = new HashMap();
/**
* Called when we receive an invocation request message. Dispatches
* the request to the appropriate invocation provider via the
* registered invocation dispatcher.
*/
protected void dispatchRequest (
int clientOid, int invCode, int methodId, Object[] args)
{
// make sure the client is still around
ClientObject source = (ClientObject)_omgr.getObject(clientOid);
if (source == null) {
Log.warning("Client no longer around for invocation " +
"request [clientOid=" + clientOid +
", code=" + invCode + ", methId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
return;
}
// look up the dispatcher
InvocationDispatcher disp = (InvocationDispatcher)
_dispatchers.get(invCode);
if (disp == null) {
Log.warning("Received invocation request for which we have " +
"no registered dispatcher [code=" + invCode +
", methId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
return;
}
// scan the args, initializing any listeners and keeping track of
// the "primary" listener
ListenerMarshaller rlist = null;
int acount = args.length;
for (int ii = 0; ii < acount; ii++) {
Object arg = args[ii];
if (arg instanceof ListenerMarshaller) {
ListenerMarshaller list = (ListenerMarshaller)arg;
list.omgr = _omgr;
// keep track of the listener we'll inform if anything
// goes horribly awry
if (rlist == null) {
rlist = list;
}
}
}
// Log.info("Dispatching invreq [caller=" + source.who() +
// ", methId=" + methodId +
// ", args=" + StringUtil.toString(args) + "].");
// dispatch the request
try {
disp.dispatchRequest(source, methodId, args);
} catch (InvocationException ie) {
if (rlist != null) {
rlist.requestFailed(ie.getMessage());
} else {
Log.warning("Service request failed but we've got no " +
"listener to inform of the failure " +
"[clientOid=" + clientOid + ", code=" + invCode +
", dispatcher=" + disp + ", methodId=" + methodId +
", args=" + StringUtil.toString(args) +
", error=" + ie + "].");
}
} catch (Throwable t) {
Log.warning("Dispatcher choked [disp=" + disp +
", methId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
Log.logStackTrace(t);
}
}
/**
* Used to generate monotonically increasing provider ids.
*/
protected synchronized int nextInvCode ()
{
return _invCode++;
}
/** The distributed object manager with which we're working. */
protected RootDObjectManager _omgr;
/** The object id of the object on which we receive invocation service
* requests. */
protected int _invoid = -1;
/** Used to generate monotonically increasing provider ids. */
protected int _invCode;
/** A table of invocation dispatchers each mapped by a unique code. */
protected HashIntMap _dispatchers = new HashIntMap();
/** Used to keep track of marshallers registered before we had our
* invocation object so that we can fill their invocation id in
* belatedly. */
protected ArrayList _lateInitQueue = new ArrayList();
/** The text that is appended to the procedure name when automatically
* generating a failure response. */
@@ -1,151 +1,11 @@
//
// $Id: InvocationProvider.java,v 1.7 2002/04/17 18:20:04 mdb Exp $
// $Id: InvocationProvider.java,v 1.8 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
import com.samskivert.util.StringUtil;
import com.threerings.presents.Log;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationObject;
import com.threerings.presents.dobj.MessageEvent;
/**
* Invocation providers should extend this class when implementing
* invocation services. Because the service procedures are identified by
* strings and the methods that are invoked are looked up via reflection,
* the derived class doesn't override or implement any particular method.
* However, the procedure names are still restricted. For example, a
* procedure identified by the name <code>Tell</code> would result in the
* invocation of a method named <code>handleTellRequest</code>. The
* arguments to that method would be defined by the arguments that
* accompanied the <code>Tell</code> invocation request along with the
* client object of the client that made the request. Specifically:
*
* <pre>
* // 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 void handleTestRequest (ClientObject source, int invid,
* String one, int two)
* {
* // ...
* }
* }
* </pre>
*
* If the arguments do not match, a reflection error will happen when
* trying to invoke the method and the whole request will fail.
*
* <p> Invocation procedures must also package up their response in a
* particular way which is through the use of the
* <code>sendResponse</code> methods. These take a response identifier
* (which determines the name of the method that will be invoked on the
* response target object provided in the client) and a variable number of
* arguments. If a response was created with the identifier
* <code>TellFailed</code>, that would result in the method
* <code>handleTellFailed</code> being invoked on the response target
* object in the client. Again the arguments much match exactly and follow
* the reflection rules for automatic conversion of primitive types
* (supply an <code>Integer</code> object for <code>int</code> params,
* etc.).
*
* <p> Note that if an invocation service method throws a {@link
* ServiceFailedException}, the invocation manager will automatically
* issue a response to the client with the string <code>Failed</code>
* appended to the request method. For example, if the client issues a
* request for <code>Foo</code> which results in a call to
* <code>handleFooRequest</code>, which throws a service failed exception,
* the server will automatically issue a failure response named
* <code>FooFailed</code> with the single argument being the
* <code>reason</code> string provided to the constructor of the service
* failed exception. The caller would then implement:
*
* <pre>
* public void handleFooFailed (int invid, String reason)
* </pre>
*
* to handle the failure.
* All invocation providers must implement this placeholder interface.
*/
public class InvocationProvider
public interface InvocationProvider
{
/**
* Delivers an invocation response properly configured with the
* supplied name and no arguments.
*/
protected void sendResponse (ClientObject source, int invid, String name)
{
deliverResponse(source, new Object[] { name, new Integer(invid) });
}
/**
* Delivers an invocation response properly configured with the
* supplied name and single argument.
*/
protected void sendResponse (ClientObject source, int invid,
String name, Object arg)
{
Object[] args = new Object[] { name, new Integer(invid), arg };
deliverResponse(source, args);
}
/**
* Delivers an invocation response properly configured with the
* supplied name and two arguments.
*/
protected void sendResponse (ClientObject source, int invid,
String name, Object arg1, Object arg2)
{
Object[] args = new Object[] {
name, new Integer(invid), arg1, arg2 };
deliverResponse(source, args);
}
/**
* Delivers an invocation response properly configured with the
* supplied name and three arguments.
*/
protected void sendResponse (ClientObject source, int invid,
String name, Object arg1, Object arg2,
Object arg3)
{
Object[] args = new Object[] {
name, new Integer(invid), arg1, arg2, arg3 };
deliverResponse(source, args);
}
/**
* Delivers an invocation response properly configured with the
* supplied name and varying number of arguments.
*/
protected void sendResponse (ClientObject source, int invid,
String name, Object[] args)
{
Object[] rargs = new Object[args.length+2];
rargs[0] = name;
rargs[1] = new Integer(invid);
System.arraycopy(args, 0, rargs, 2, args.length);
deliverResponse(source, rargs);
}
protected void deliverResponse (ClientObject source, Object[] args)
{
// make sure they didn't go away in the meanwhile
if (source.isActive()) {
// create the response event
MessageEvent mevt = new MessageEvent(
source.getOid(), InvocationObject.RESPONSE_NAME, args);
// and ship it off
PresentsServer.omgr.postEvent(mevt);
} else {
Log.warning("Dropping invrsp due to disappearing client " +
"[cloid=" + source.getOid() +
", args=" + StringUtil.toString(args) + "].");
}
}
}
@@ -0,0 +1,45 @@
//
// $Id: InvocationSender.java,v 1.1 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
import com.samskivert.util.StringUtil;
import com.threerings.presents.Log;
import com.threerings.presents.client.InvocationReceiver.Registration;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.InvocationNotificationEvent;
/**
* Provides basic functionality used by all invocation sender classes.
*/
public abstract class InvocationSender
{
/**
* Requests that the specified invocation notification be packaged up
* and sent to the supplied target client.
*/
public static void sendNotification (
ClientObject target, String receiverCode, int methodId, Object[] args)
{
// convert the receiver hash id into the code used on this
// specific client
Registration rreg = (Registration)target.receivers.get(receiverCode);
if (rreg == null) {
Log.warning("Unable to locate registered receiver for " +
"invocation service notification [target=" + target +
", code=" + receiverCode + ", methId=" + methodId +
", args=" + StringUtil.toString(args) + "].");
} else {
// Log.info("Sending notification [target=" + target +
// ", code=" + receiverCode + ", methodId=" + methodId +
// ", args=" + StringUtil.toString(args) + "].");
// create and dispatch an invocation notification event
target.postEvent(
new InvocationNotificationEvent(
target.getOid(), rreg.receiverId, methodId, args));
}
}
}
@@ -1,5 +1,5 @@
//
// $Id: PresentsClient.java,v 1.34 2002/07/23 05:52:49 mdb Exp $
// $Id: PresentsClient.java,v 1.35 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
@@ -324,8 +324,8 @@ public class PresentsClient
// give them the client object id
data.clientOid = _clobj.getOid();
// give them the invocation oid
data.invOid = PresentsServer.invmgr.getOid();
// fill in the list of bootstrap services
data.services = PresentsServer.invmgr.bootlist;
}
/**
@@ -1,5 +1,5 @@
//
// $Id: PresentsServer.java,v 1.23 2002/07/25 20:24:02 mdb Exp $
// $Id: PresentsServer.java,v 1.24 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
@@ -66,57 +66,57 @@ public class PresentsServer
// initialize the time base services
TimeBaseProvider.init(invmgr, omgr);
// register our invocation service providers
registerProviders(PresentsConfig.getProviders());
// // register our invocation service providers
// registerProviders(PresentsConfig.getProviders());
}
/**
* Registers invocation service providers as parsed from a
* configuration file. Each string in the array should contain an
* expression of the form:
*
* <pre>
* module = provider fully-qualified class name
* </pre>
*
* A comma separated list of these can be specified in the
* configuration file and loaded into a string array easily. These
* providers will be instantiated and registered with the invocation
* manager.
*/
protected void registerProviders (String[] providers)
{
// ignore null arrays to make life easier for the caller
if (providers == null) {
return;
}
// /**
// * Registers invocation service providers as parsed from a
// * configuration file. Each string in the array should contain an
// * expression of the form:
// *
// * <pre>
// * module = provider fully-qualified class name
// * </pre>
// *
// * A comma separated list of these can be specified in the
// * configuration file and loaded into a string array easily. These
// * providers will be instantiated and registered with the invocation
// * manager.
// */
// protected void registerProviders (String[] providers)
// {
// // ignore null arrays to make life easier for the caller
// if (providers == null) {
// return;
// }
for (int i = 0; i < providers.length; i++) {
int eidx = providers[i].indexOf("=");
if (eidx == -1) {
Log.warning("Ignoring bogus provider declaration " +
"[decl=" + providers[i] + "].");
continue;
}
// for (int i = 0; i < providers.length; i++) {
// int eidx = providers[i].indexOf("=");
// if (eidx == -1) {
// Log.warning("Ignoring bogus provider declaration " +
// "[decl=" + providers[i] + "].");
// continue;
// }
String module = providers[i].substring(0, eidx).trim();
String pname = providers[i].substring(eidx+1).trim();
// String module = providers[i].substring(0, eidx).trim();
// String pname = providers[i].substring(eidx+1).trim();
// instantiate the provider class and register it
try {
Class pclass = Class.forName(pname);
InvocationProvider provider = (InvocationProvider)
pclass.newInstance();
invmgr.registerProvider(module, provider);
Log.info("Registered provider [module=" + module +
", provider=" + pname + "].");
// // instantiate the provider class and register it
// try {
// Class pclass = Class.forName(pname);
// InvocationProvider provider = (InvocationProvider)
// pclass.newInstance();
// invmgr.registerProvider(module, provider);
// Log.info("Registered provider [module=" + module +
// ", provider=" + pname + "].");
} catch (Exception e) {
Log.warning("Unable to register provider [module=" + module +
", provider=" + pname + ", error=" + e + "].");
}
}
}
// } catch (Exception e) {
// Log.warning("Unable to register provider [module=" + module +
// ", provider=" + pname + ", error=" + e + "].");
// }
// }
// }
/**
* Starts up all of the server services and enters the main server
@@ -1,45 +0,0 @@
//
// $Id: ServiceFailedException.java,v 1.2 2001/10/11 04:07:53 mdb Exp $
package com.threerings.presents.server;
/**
* An exception class for use in concert with invocation services when
* they need to communicate a failure of some kind and can't use the
* return value.
*
* <p> For example, consider an invitation service:
*
* <pre>
* public class fooManager
* {
* // returns invitation id, throws ServiceFailedException if
* // invitation couldn't be processed
* public int invite (...)
* throws ServiceFailedException
* {
* }
* }
*
* public class fooProvider
* {
* public void handleInviteRequest (...)
* {
* try {
* int inviteId = _mgr.invite(...);
* sendResponse(..., INVITE_RECEIVED, new Integer(inviteId));
*
* } catch (ServiceFailedException sfe) {
* sendResponse(..., INVITE_FAILED, sfe.getMessage());
* }
* }
* }
* </pre>
*/
public class ServiceFailedException extends Exception
{
public ServiceFailedException (String message)
{
super(message);
}
}
@@ -0,0 +1,52 @@
//
// $Id: TimeBaseDispatcher.java,v 1.1 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.TimeBaseService;
import com.threerings.presents.client.TimeBaseService.GotTimeBaseListener;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.data.TimeBaseMarshaller;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
/**
* Dispatches requests to the {@link TimeBaseProvider}.
*/
public class TimeBaseDispatcher extends InvocationDispatcher
{
/**
* Creates a dispatcher that may be registered to dispatch invocation
* service requests for the specified provider.
*/
public TimeBaseDispatcher (TimeBaseProvider provider)
{
this.provider = provider;
}
// documentation inherited
public InvocationMarshaller createMarshaller ()
{
return new TimeBaseMarshaller();
}
// documentation inherited
public void dispatchRequest (
ClientObject source, int methodId, Object[] args)
throws InvocationException
{
switch (methodId) {
case TimeBaseMarshaller.GET_TIME_OID:
((TimeBaseProvider)provider).getTimeOid(
source,
(String)args[0], (GotTimeBaseListener)args[1]
);
return;
default:
super.dispatchRequest(source, methodId, args);
}
}
}
@@ -1,5 +1,5 @@
//
// $Id: TimeBaseProvider.java,v 1.1 2002/05/28 23:14:06 mdb Exp $
// $Id: TimeBaseProvider.java,v 1.2 2002/08/14 19:07:56 mdb Exp $
package com.threerings.presents.server;
@@ -7,6 +7,8 @@ import java.util.HashMap;
import com.samskivert.util.ResultListener;
import com.threerings.presents.Log;
import com.threerings.presents.client.TimeBaseService.GotTimeBaseListener;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.data.TimeBaseCodes;
import com.threerings.presents.data.TimeBaseObject;
@@ -22,8 +24,8 @@ import com.threerings.presents.dobj.Subscriber;
* network which are expanded based on a shared base time into full time
* stamps.
*/
public class TimeBaseProvider extends InvocationProvider
implements TimeBaseCodes
public class TimeBaseProvider
implements InvocationProvider, TimeBaseCodes
{
/**
* Registers the time provider with the appropriate managers. Called
@@ -35,8 +37,9 @@ public class TimeBaseProvider extends InvocationProvider
_invmgr = invmgr;
_omgr = omgr;
// register an invocation provider instance
_invmgr.registerProvider(MODULE_NAME, new TimeBaseProvider());
// register a provider instance
invmgr.registerDispatcher(
new TimeBaseDispatcher(new TimeBaseProvider()), true);
}
/**
@@ -81,18 +84,17 @@ public class TimeBaseProvider extends InvocationProvider
* Processes a request from a client to fetch the oid of the specified
* time object.
*/
public void handleGetTimeOidRequest (
ClientObject source, int invid, String timeBase)
throws ServiceFailedException
public void getTimeOid (
ClientObject source, String timeBase, GotTimeBaseListener listener)
throws InvocationException
{
// look up the time base object in question
TimeBaseObject time = getTimeBase(timeBase);
if (time == null) {
throw new ServiceFailedException(NO_SUCH_TIME_BASE);
throw new InvocationException(NO_SUCH_TIME_BASE);
}
// and send the response
sendResponse(source, invid, TIME_OID_RESPONSE,
new Integer(time.getOid()));
listener.gotTimeOid(time.getOid());
}
/** Used to keep track of our time base objects. */