diff --git a/src/java/com/threerings/crowd/server/CrowdClient.java b/src/java/com/threerings/crowd/server/CrowdClient.java
index 04e48eabc..a8a4aaa78 100644
--- a/src/java/com/threerings/crowd/server/CrowdClient.java
+++ b/src/java/com/threerings/crowd/server/CrowdClient.java
@@ -1,45 +1,16 @@
//
-// $Id: CrowdClient.java,v 1.7 2002/02/20 23:35:42 mdb Exp $
+// $Id: CrowdClient.java,v 1.8 2002/03/05 05:33:25 mdb Exp $
package com.threerings.crowd.server;
import com.threerings.presents.server.PresentsClient;
-import com.threerings.crowd.data.BodyObject;
/**
- * The crowd client extends the presents client and does some
- * initializations necessary for the crowd services.
+ * The crowd client extends the presents client but doesn't really do
+ * anything at present. It exists mainly so that implementation systems
+ * will extend it and ensure that we have the option of adding
+ * functionality here in the future.
*/
public class CrowdClient extends PresentsClient
{
- protected void sessionWillStart (Object authInfo)
- {
- super.sessionWillStart(authInfo);
-
- // cast our client object to a body object
- _bodobj = (BodyObject)_clobj;
-
- // and configure our username
- _bodobj.setUsername(_username);
-
- // register our body object mapping
- CrowdServer.mapBody(_username, _bodobj);
- }
-
- protected void sessionWillResume (Object authInfo)
- {
- super.sessionWillResume(authInfo);
-
- // nothing to do here presently
- }
-
- protected void sessionDidTerminate ()
- {
- super.sessionDidTerminate();
-
- // unregister our body object mapping
- CrowdServer.unmapBody(_username);
- }
-
- protected BodyObject _bodobj;
}
diff --git a/src/java/com/threerings/crowd/server/CrowdClientResolver.java b/src/java/com/threerings/crowd/server/CrowdClientResolver.java
new file mode 100644
index 000000000..29eb2a009
--- /dev/null
+++ b/src/java/com/threerings/crowd/server/CrowdClientResolver.java
@@ -0,0 +1,29 @@
+//
+// $Id: CrowdClientResolver.java,v 1.1 2002/03/05 05:33:25 mdb Exp $
+
+package com.threerings.crowd.server;
+
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.server.ClientResolver;
+
+import com.threerings.crowd.data.BodyObject;
+
+/**
+ * Used to configure crowd-specific client object data.
+ */
+public class CrowdClientResolver extends ClientResolver
+{
+ // documentation inherited
+ public Class getClientObjectClass ()
+ {
+ return BodyObject.class;
+ }
+
+ // documentation inherited
+ protected void resolveClientData (ClientObject clobj)
+ throws Exception
+ {
+ // just fill in the username
+ ((BodyObject)clobj).username = _username;
+ }
+}
diff --git a/src/java/com/threerings/crowd/server/CrowdServer.java b/src/java/com/threerings/crowd/server/CrowdServer.java
index b25d06ed0..be85b2224 100644
--- a/src/java/com/threerings/crowd/server/CrowdServer.java
+++ b/src/java/com/threerings/crowd/server/CrowdServer.java
@@ -1,5 +1,5 @@
//
-// $Id: CrowdServer.java,v 1.9 2001/12/04 01:02:59 mdb Exp $
+// $Id: CrowdServer.java,v 1.10 2002/03/05 05:33:25 mdb Exp $
package com.threerings.crowd.server;
@@ -34,11 +34,11 @@ public class CrowdServer extends PresentsServer
// bind the crowd server config into the namespace
config.bindProperties(CONFIG_KEY, CONFIG_PATH, true);
- // configure the client to use our crowd client
+ // configure the client manager to use our client
clmgr.setClientClass(CrowdClient.class);
- // configure the client to use the body object
- clmgr.setClientObjectClass(BodyObject.class);
+ // configure the client manager to use our resolver
+ clmgr.setClientResolverClass(CrowdClientResolver.class);
// create our place registry
plreg = new PlaceRegistry(config, invmgr, omgr);
@@ -50,31 +50,13 @@ public class CrowdServer extends PresentsServer
}
/**
- * The crowd server maintains a mapping of username to body object for
- * all active users on the server. This should only be called from the
+ * The server maintains a mapping of username to body object for all
+ * active users on the server. This should only be called from the
* dobjmgr thread.
*/
public static BodyObject lookupBody (String username)
{
- return (BodyObject)_bodymap.get(username);
- }
-
- /**
- * Called by the crowd client to map a username to a particular body
- * object. This should only be called from the dobjmgr thread.
- */
- protected static void mapBody (String username, BodyObject bodobj)
- {
- _bodymap.put(username, bodobj);
- }
-
- /**
- * Called by the crowd client to unmap a username from a particular
- * body object. This should only be called from the dobjmgr thread.
- */
- protected static void unmapBody (String username)
- {
- _bodymap.remove(username);
+ return (BodyObject)clmgr.getClientObject(username);
}
public static void main (String[] args)
diff --git a/src/java/com/threerings/micasa/simulator/server/SimpleServer.java b/src/java/com/threerings/micasa/simulator/server/SimpleServer.java
index 53985895e..5cb0d59fd 100644
--- a/src/java/com/threerings/micasa/simulator/server/SimpleServer.java
+++ b/src/java/com/threerings/micasa/simulator/server/SimpleServer.java
@@ -1,5 +1,5 @@
//
-// $Id: SimpleServer.java,v 1.2 2002/02/05 22:57:10 mdb Exp $
+// $Id: SimpleServer.java,v 1.3 2002/03/05 05:33:25 mdb Exp $
package com.threerings.micasa.simulator.server;
@@ -23,10 +23,4 @@ public class SimpleServer extends CrowdServer
SimulatorManager simmgr = new SimulatorManager();
simmgr.init(config, invmgr, plreg, clmgr, omgr, this);
}
-
- // documentation inherited
- public void fakeBodyMapping (String username, BodyObject bodobj)
- {
- _bodymap.put(username, bodobj);
- }
}
diff --git a/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java b/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java
index 8fd8b251f..c264dfee0 100644
--- a/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java
+++ b/src/java/com/threerings/micasa/simulator/server/SimulatorManager.java
@@ -1,5 +1,5 @@
//
-// $Id: SimulatorManager.java,v 1.6 2002/02/05 22:12:42 mdb Exp $
+// $Id: SimulatorManager.java,v 1.7 2002/03/05 05:33:25 mdb Exp $
package com.threerings.micasa.simulator.server;
@@ -7,12 +7,10 @@ import java.util.ArrayList;
import com.samskivert.util.Config;
-import com.threerings.presents.dobj.DObject;
-import com.threerings.presents.dobj.ObjectAccessException;
-import com.threerings.presents.dobj.Subscriber;
+import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.RootDObjectManager;
-
import com.threerings.presents.server.ClientManager;
+import com.threerings.presents.server.ClientResolutionListener;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider;
@@ -116,20 +114,13 @@ public class SimulatorManager
// cast the place to the game object for the game we're creating
_gobj = (GameObject)place;
- // create a subscriber to await creation of each simulant body
- // object
- Subscriber sub = new Subscriber() {
- public void objectAvailable (DObject object)
+ // resolve the simulant body objects
+ ClientResolutionListener listener = new ClientResolutionListener()
+ {
+ public void clientResolved (String username, ClientObject clobj)
{
- // set up the simulant's body object
- BodyObject bobj = (BodyObject)object;
- bobj.username = "simulant" + (_sims.size() + 1);
-
- // map the simulant into the server body set
- _simserv.fakeBodyMapping(bobj.username, bobj);
-
- // hold onto it for later game creation
- _sims.add(bobj);
+ // hold onto the body object for later game creation
+ _sims.add(clobj);
// create the game if we've received all body objects
if (_sims.size() == (_playerCount - 1)) {
@@ -137,18 +128,17 @@ public class SimulatorManager
}
}
- public void requestFailed (
- int oid, ObjectAccessException cause)
+ public void resolutionFailed (String username, Exception cause)
{
Log.warning("Unable to create simulant body object " +
"[error=" + cause + "].");
}
};
- // fire off simulant body object creation requests
- Class simobjClass = _clmgr.getClientObjectClass();
+ // resolve client objects for all of our simulants
for (int ii = 1; ii < _playerCount; ii++) {
- _omgr.createObject(simobjClass, sub);
+ String username = "simulant" + (_sims.size() + 1);
+ _clmgr.resolveClientObject(username, listener);
}
}
diff --git a/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java b/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java
index 95b0dc986..5a8f8b0e3 100644
--- a/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java
+++ b/src/java/com/threerings/micasa/simulator/server/SimulatorServer.java
@@ -1,5 +1,5 @@
//
-// $Id: SimulatorServer.java,v 1.5 2002/02/05 22:12:42 mdb Exp $
+// $Id: SimulatorServer.java,v 1.6 2002/03/05 05:33:25 mdb Exp $
package com.threerings.micasa.simulator.server;
@@ -25,10 +25,4 @@ public interface SimulatorServer
* primary business.
*/
public void run ();
-
- /**
- * Called by the simulator manager to map a username to a particular
- * body object. This should only be called from the dobjmgr thread.
- */
- public void fakeBodyMapping (String username, BodyObject bodobj);
}
diff --git a/src/java/com/threerings/presents/server/Authenticator.java b/src/java/com/threerings/presents/server/Authenticator.java
index aedc7ffb9..71204a07a 100644
--- a/src/java/com/threerings/presents/server/Authenticator.java
+++ b/src/java/com/threerings/presents/server/Authenticator.java
@@ -1,11 +1,14 @@
//
-// $Id: Authenticator.java,v 1.4 2002/03/05 03:19:18 mdb Exp $
+// $Id: Authenticator.java,v 1.5 2002/03/05 05:33:25 mdb Exp $
-package com.threerings.presents.server.net;
+package com.threerings.presents.server;
import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.AuthResponseData;
+import com.threerings.presents.server.net.AuthingConnection;
+import com.threerings.presents.server.net.ConnectionManager;
+
/**
* The authenticator is a pluggable component of the authentication
* framework. The base class handles the basic mechanics of authentication
diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java
index 7fe6d74ea..007b4211f 100644
--- a/src/java/com/threerings/presents/server/ClientManager.java
+++ b/src/java/com/threerings/presents/server/ClientManager.java
@@ -1,5 +1,5 @@
//
-// $Id: ClientManager.java,v 1.13 2001/12/03 22:01:57 mdb Exp $
+// $Id: ClientManager.java,v 1.14 2002/03/05 05:33:25 mdb Exp $
package com.threerings.presents.server;
@@ -26,6 +26,10 @@ import com.threerings.presents.server.net.*;
*/
public class ClientManager implements ConnectionObserver
{
+ /**
+ * Constructs a client manager that will interact with the supplied
+ * connection manager.
+ */
public ClientManager (ConnectionManager conmgr)
{
// register ourselves as a connection observer
@@ -34,10 +38,8 @@ public class ClientManager implements ConnectionObserver
/**
* Instructs the client manager to construct instances of this derived
- * class of PresentsClient to managed newly accepted client
+ * class of {@link PresentsClient} to managed newly accepted client
* connections.
- *
- * @see PresentsClient
*/
public void setClientClass (Class clientClass)
{
@@ -54,35 +56,103 @@ public class ClientManager implements ConnectionObserver
}
/**
- * Instructs the client to create an instance of this
- * ClientObject derived class when creating the
- * distributed object that corresponds to a particular client session.
- *
- * @see com.threerings.presents.data.ClientObject
+ * Instructs the client to use instances of this {@link
+ * ClientResolver} derived class when resolving clients in preparation
+ * for starting a client session.
*/
- public void setClientObjectClass (Class clobjClass)
+ public void setClientResolverClass (Class clrClass)
{
// sanity check
- if (!ClientObject.class.isAssignableFrom(clobjClass)) {
- Log.warning("Requested to use client object class that does " +
- "not derive from ClientObject " +
- "[class=" + clobjClass.getName() + "].");
- return;
- }
+ if (!ClientResolver.class.isAssignableFrom(clrClass)) {
+ Log.warning("Requested to use client resolver class that does " +
+ "not derive from ClientResolver " +
+ "[class=" + clrClass.getName() + "].");
- // make a note of it
- _clobjClass = clobjClass;
+ } else {
+ // make a note of it
+ _clrClass = clrClass;
+ }
}
/**
- * Returns the class that should be used when creating a distributed
- * object to accompany a particular client session. In general, this
- * is only used by the PresentsClient object when it is
- * setting up a client's session for the first time.
+ * Returns the client object associated with the specified username.
+ * This will return null unless the client object is resolved for some
+ * reason (like they are logged on).
*/
- public Class getClientObjectClass ()
+ public ClientObject getClientObject (String username)
{
- return _clobjClass;
+ return (ClientObject)_objmap.get(username);
+ }
+
+ /**
+ * Requests that the client object for the specified user be resolved.
+ * If the client object is already resolved, the request will be
+ * processed immediately, otherwise the appropriate client object will
+ * be instantiated and populated by the registered client resolver
+ * (which may involve talking to databases).
+ */
+ public synchronized void resolveClientObject (
+ String username, ClientResolutionListener listener)
+ {
+ // look to see if the client object is already resolved
+ ClientObject clobj = (ClientObject)_objmap.get(username);
+ if (clobj != null) {
+ listener.clientResolved(username, clobj);
+ return;
+ }
+
+ // look to see if it's currently being resolved
+ ClientResolver clr = (ClientResolver)_penders.get(username);
+ if (clr != null) {
+ // throw this guy onto the bandwagon
+ clr.addResolutionListener(listener);
+ return;
+ }
+
+ try {
+ // create a client resolver instance which will create our
+ // client object, populate it and notify the listeners
+ clr = (ClientResolver)_clrClass.newInstance();
+ clr.init(username);
+ clr.addResolutionListener(listener);
+
+ // request that the appropriate client object be created by
+ // the dobject manager which starts the whole business off
+ PresentsServer.omgr.createObject(clr.getClientObjectClass(), clr);
+
+ } catch (Exception e) {
+ // let the listener know that we're hosed
+ listener.resolutionFailed(username, e);
+ }
+ }
+
+ /**
+ * Called by the {@link ClientResolver} once a client object has been
+ * resolved.
+ */
+ protected synchronized void mapClientObject (
+ String username, ClientObject clobj)
+ {
+ // stuff the object into the mapping table
+ _objmap.put(username, clobj);
+
+ // and remove the resolution listener
+ _penders.remove(username);
+ }
+
+ /**
+ * If an entity resolves a client object outside the scope of a normal
+ * client session, it should call this to unmap the client object when
+ * it's finished. This won't actually unmap the client if someone came
+ * along and started a session for that client in the meanwhile.
+ */
+ public synchronized void unmapClientObject (String username)
+ {
+ // we only remove the mapping if there's not a session in progress
+ // (which is indicated by a mapping in the usermap table)
+ if (!_usermap.containsKey(username)) {
+ _objmap.remove(username);
+ }
}
// documentation inherited
@@ -98,7 +168,7 @@ public class ClientManager implements ConnectionObserver
if (client != null) {
Log.info("Session resumed [username=" + username +
", conn=" + conn + "].");
- client.resumeSession(conn, rsp.getAuthInfo());
+ client.resumeSession(conn);
} else {
Log.info("Session initiated [username=" + username +
@@ -106,8 +176,9 @@ public class ClientManager implements ConnectionObserver
// create a new client and stick'em in the table
try {
client = (PresentsClient)_clientClass.newInstance();
- client.startSession(this, username, conn, rsp.getAuthInfo());
+ client.startSession(this, username, conn);
_usermap.put(username, client);
+
} catch (Exception e) {
Log.warning("Failed to instantiate client instance to " +
"manage new client connection " +
@@ -162,26 +233,39 @@ public class ClientManager implements ConnectionObserver
*/
synchronized void clientDidEndSession (PresentsClient client)
{
+ String username = client.getUsername();
// remove the client from the username map
- PresentsClient rc = (PresentsClient)_usermap.remove(client.getUsername());
+ PresentsClient rc = (PresentsClient)_usermap.remove(username);
+ // and the client object mapping as well
+ _objmap.remove(username);
- // sanity check because we can
+ // sanity check just because we can
if (rc == null) {
Log.warning("Unregistered client ended session " +
"[client=" + client + "].");
-
} else if (rc != client) {
Log.warning("Different clients with same username!? " +
"[c1=" + rc + ", c2=" + client + "].");
-
} else {
Log.info("Ending session [client=" + client + "].");
}
}
+ /** A mapping from usernames to client instances. */
protected HashMap _usermap = new HashMap();
+
+ /** A mapping from connections to client instances. */
protected HashMap _conmap = new HashMap();
+ /** A mapping from usernames to client object instances. */
+ protected HashMap _objmap = new HashMap();
+
+ /** A mapping of pending client resolvers. */
+ protected HashMap _penders = new HashMap();
+
+ /** The client class in use. */
protected Class _clientClass = PresentsClient.class;
- protected Class _clobjClass = ClientObject.class;
+
+ /** The client resolver class in use. */
+ protected Class _clrClass = ClientResolver.class;
}
diff --git a/src/java/com/threerings/presents/server/ClientResolutionListener.java b/src/java/com/threerings/presents/server/ClientResolutionListener.java
new file mode 100644
index 000000000..4ca8f8b46
--- /dev/null
+++ b/src/java/com/threerings/presents/server/ClientResolutionListener.java
@@ -0,0 +1,25 @@
+//
+// $Id: ClientResolutionListener.java,v 1.1 2002/03/05 05:33:25 mdb Exp $
+
+package com.threerings.presents.server;
+
+import com.threerings.presents.data.ClientObject;
+
+/**
+ * Entites that wish to resolve client objects must implement this
+ * interface so as to partake in the asynchronous process of client
+ * object resolution.
+ */
+public interface ClientResolutionListener
+{
+ /**
+ * Called when resolution completed successfully.
+ */
+ public void clientResolved (String username, ClientObject clobj);
+
+ /**
+ * Called when resolution fails.
+ */
+ public void resolutionFailed (String username, Exception reason);
+}
+
diff --git a/src/java/com/threerings/presents/server/ClientResolver.java b/src/java/com/threerings/presents/server/ClientResolver.java
new file mode 100644
index 000000000..f6a1f922b
--- /dev/null
+++ b/src/java/com/threerings/presents/server/ClientResolver.java
@@ -0,0 +1,158 @@
+//
+// $Id: ClientResolver.java,v 1.1 2002/03/05 05:33:25 mdb Exp $
+
+package com.threerings.presents.server;
+
+import java.util.ArrayList;
+
+import com.threerings.presents.Log;
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.dobj.ObjectAccessException;
+import com.threerings.presents.dobj.Subscriber;
+import com.threerings.presents.util.Invoker;
+
+/**
+ * Used to resolve client data when a user starts a session (or when some
+ * other entity needs access to a client object). Implementations will
+ * want to extend this class and override {@link #resolveClientData},
+ * making the necessary database calls and populating the client object
+ * appropriately.
+ */
+public class ClientResolver extends Invoker.Unit
+ implements Subscriber
+{
+ /**
+ * Initiailizes this instance.
+ *
+ * @param username the username of the user to be resolved.
+ */
+ public void init (String username)
+ {
+ _username = username;
+ }
+
+ /**
+ * Adds a resolution listener to this active resolver.
+ */
+ public void addResolutionListener (ClientResolutionListener listener)
+ {
+ _listeners.add(listener);
+ }
+
+ /**
+ * Returns the {@link ClientObject} derived class that should be
+ * created to kick off the resolution process.
+ */
+ public Class getClientObjectClass ()
+ {
+ return ClientObject.class;
+ }
+
+ // documentation inherited
+ public void objectAvailable (DObject object)
+ {
+ // we've got our object, so shunt ourselves over to the invoker
+ // thread to perform database loading
+ _clobj = (ClientObject)object;
+ PresentsServer.invoker.postUnit(this);
+ }
+
+ // documentation inherited
+ public void requestFailed (int oid, ObjectAccessException cause)
+ {
+ // pass the buck
+ reportFailure(cause);
+ }
+
+ // documentation inherited
+ public boolean invoke ()
+ {
+ try {
+ // allow our derived class to do its database loads
+ resolveClientData(_clobj);
+ } catch (Exception cause) {
+ // keep this around until we're back on the dobj thread
+ _failure = cause;
+ }
+ return true;
+ }
+
+ // documentation inherited
+ public void handleResult ()
+ {
+ // if we failed in invoke() report it here
+ if (_failure != null) {
+ // destroy the dangling user object
+ PresentsServer.omgr.destroyObject(_clobj.getOid());
+
+ // let our listener know that we're hosed
+ reportFailure(_failure);
+
+ } else {
+ // otherwise let the client manager know that we're all clear
+ PresentsServer.clmgr.mapClientObject(_username, _clobj);
+
+ // and let the listeners in on the secret as well
+ int lcount = _listeners.size();
+ for (int i = 0; i < lcount; i++) {
+ ClientResolutionListener crl = (ClientResolutionListener)
+ _listeners.get(i);
+ try {
+ crl.clientResolved(_username, _clobj);
+ } catch (Exception e) {
+ Log.warning("Client resolution listener choked during " +
+ "resolution notification [crl=" + crl +
+ ", clobj=" + _clobj + "].");
+ Log.logStackTrace(e);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is called on the invoker thread which means that it can
+ * do things like blocking database requests and generally whatever is
+ * necessary to load up all the client data that is desired by the
+ * implentation system. Any exceptions that are thrown will be caught
+ * and reported as a failure to the client resolution listener.
+ */
+ protected void resolveClientData (ClientObject clobj)
+ throws Exception
+ {
+ // nothing to do by default
+ }
+
+ /**
+ * Reports failure to our resolution listeners.
+ */
+ protected void reportFailure (Exception cause)
+ {
+ int lcount = _listeners.size();
+ for (int i = 0; i < lcount; i++) {
+ ClientResolutionListener crl = (ClientResolutionListener)
+ _listeners.get(i);
+ try {
+ crl.resolutionFailed(_username, cause);
+ } catch (Exception e) {
+ Log.warning("Client resolution listener choked during " +
+ "failure notification [crl=" + crl +
+ ", username=" + _username +
+ ", cause=" + cause + "].");
+ Log.logStackTrace(e);
+ }
+ }
+ }
+
+ /** The name of the user whose client object is being resolved. */
+ protected String _username;
+
+ /** The entities to notify of success or failure. */
+ protected ArrayList _listeners = new ArrayList();
+
+ /** The resolving client object. */
+ protected ClientObject _clobj;
+
+ /** A place to keep an exception around for a moment. */
+ protected Exception _failure;
+}
diff --git a/src/java/com/threerings/presents/server/DummyAuthenticator.java b/src/java/com/threerings/presents/server/DummyAuthenticator.java
index d88880962..ae98e7aad 100644
--- a/src/java/com/threerings/presents/server/DummyAuthenticator.java
+++ b/src/java/com/threerings/presents/server/DummyAuthenticator.java
@@ -1,12 +1,11 @@
//
-// $Id: DummyAuthenticator.java,v 1.5 2002/03/05 03:19:18 mdb Exp $
+// $Id: DummyAuthenticator.java,v 1.6 2002/03/05 05:33:25 mdb Exp $
package com.threerings.presents.server;
import com.threerings.presents.Log;
import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.AuthResponseData;
-import com.threerings.presents.server.net.Authenticator;
import com.threerings.presents.server.net.AuthingConnection;
/**
diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java
index 258e3174b..1f29f141b 100644
--- a/src/java/com/threerings/presents/server/PresentsClient.java
+++ b/src/java/com/threerings/presents/server/PresentsClient.java
@@ -1,5 +1,5 @@
//
-// $Id: PresentsClient.java,v 1.27 2001/12/13 05:32:28 mdb Exp $
+// $Id: PresentsClient.java,v 1.28 2002/03/05 05:33:25 mdb Exp $
package com.threerings.presents.server;
@@ -52,7 +52,8 @@ import com.threerings.presents.server.net.MessageHandler;
* conmgr thread and therefore also need not be synchronized.
*/
public class PresentsClient
- implements Subscriber, EventListener, MessageHandler
+ implements Subscriber, EventListener, MessageHandler,
+ ClientResolutionListener
{
/**
* Returns the username with which this client instance is associated.
@@ -71,47 +72,40 @@ public class PresentsClient
}
/**
- * Initializes this client instance with the specified username and
- * connection instance and begins a client session.
+ * Initializes this client instance with the specified username,
+ * connection instance and client object and begins a client session.
*/
protected void startSession (
- ClientManager cmgr, String username, Connection conn,
- final Object authInfo)
+ ClientManager cmgr, String username, Connection conn)
{
_cmgr = cmgr;
_username = username;
setConnection(conn);
- // create our client object and bootstrap the client once we've
- // got it
- Subscriber sub = new Subscriber ()
- {
- public void objectAvailable (DObject object)
- {
- setClientObject((ClientObject)object);
- sessionWillStart(authInfo);
- sendBootstrap();
- }
-
- public void requestFailed (int oid, ObjectAccessException cause)
- {
- Log.warning("Unable to create client object " +
- "[client=" + PresentsClient.this +
- ", error=" + cause + "].");
- }
- };
- Class clobjClass = _cmgr.getClientObjectClass();
- PresentsServer.omgr.createObject(clobjClass, sub);
+ // resolve our client object before we get fully underway
+ cmgr.resolveClientObject(username, this);
}
- /**
- * Called when we receive our client object from the dobjmgr. We go
- * through this synchronized method to ensure that the client object
- * is visible to the conmgr.
- */
- protected synchronized void setClientObject (ClientObject clobj)
+ // documentation inherited from interface
+ public void clientResolved (String username, ClientObject clobj)
{
+ // we'll be keeping this bad boy
_clobj = clobj;
+
+ // finish up our regular business
+ sessionWillStart();
+ sendBootstrap();
+ }
+
+ // documentation inherited from interface
+ public void resolutionFailed (String username, Exception reason)
+ {
+ // urk; nothing to do but complain and get the f**k out of dodge
+ Log.warning("Unable to resolve client [username=" + username + "].");
+ Log.logStackTrace(reason);
+
+ // end the session now to prevent danglage
+ endSession();
}
/**
@@ -119,7 +113,7 @@ public class PresentsClient
* authenticates as this already established client. This must only be
* called from the congmr thread.
*/
- protected void resumeSession (Connection conn, final Object authInfo)
+ protected void resumeSession (Connection conn)
{
Connection oldconn = getConnection();
@@ -145,7 +139,7 @@ public class PresentsClient
{
// now that we're on the dobjmgr thread we can resume our
// session resumption
- finishResumeSession(authInfo);
+ finishResumeSession();
return false;
}
};
@@ -157,10 +151,10 @@ public class PresentsClient
* resumption. We call some call backs and send the bootstrap info to
* the client.
*/
- protected void finishResumeSession (Object authInfo)
+ protected void finishResumeSession ()
{
// let derived classes do any session resuming
- sessionWillResume(authInfo);
+ sessionWillResume();
// send off a bootstrap notification immediately because we've
// already got our client object
@@ -170,38 +164,20 @@ public class PresentsClient
}
/**
- * Requests that the client session be terminated. This is normally
- * invoked as a result of a logoff request by the client. It should
- * only be invoked from the conmgr thread.
+ * Forcibly terminates a client's session. This must be called from
+ * the dobjmgr thread.
*/
public void endSession ()
{
- // close our connection (which results in everything being
- // properly unregistered)
+ // queue up a request for our connection to be closed (if we have
+ // a connection, that is)
Connection conn = getConnection();
if (conn != null) {
- conn.close();
- } else {
- Log.info("Unable to close connection for logoff request " +
- "[client=" + this + "].");
+ PresentsServer.conmgr.closeConnection(conn);
}
- // then let the client manager know what's up
- _cmgr.clientDidEndSession(this);
- // we need to get onto the distributed object thread so that we
- // can finalize the ending of the session. we do so by posting a
- // special event
- DEvent event = new DEvent(0)
- {
- public boolean applyToObject (DObject target)
- {
- // now that we're on the dobjmgr thread we can call into
- // our derived classes to finish the session termination
- sessionDidTerminate();
- return false;
- }
- };
- PresentsServer.omgr.postEvent(event);
+ // and clean up after ourselves
+ sessionDidEnd();
}
/**
@@ -260,11 +236,8 @@ public class PresentsClient
*
Note: This function will be called on the dobjmgr * thread which means that object manipulations are OK, but client * instance manipulations must done carefully. - * - * @param authInfo optional information provided by the authenticator - * that it obtained during the authentication phase. */ - protected void sessionWillStart (Object authInfo) + protected void sessionWillStart () { } @@ -278,11 +251,8 @@ public class PresentsClient *
Note: This function will be called on the dobjmgr
* thread which means that object manipulations are OK, but client
* instance manipulations must done carefully.
- *
- * @param authInfo optional information provided by the authenticator
- * that it obtained during the authentication phase.
*/
- protected void sessionWillResume (Object authInfo)
+ protected void sessionWillResume ()
{
}
@@ -290,14 +260,17 @@ public class PresentsClient
* Called when the client session ends (either because the client
* logged off or because the server forcibly terminated the session).
* Derived classes that override this method should be sure to call
- * super.sessionDidTerminate.
+ * super.sessionDidEnd.
*
*
Note: This function will be called on the dobjmgr * thread which means that object manipulations are OK, but client * instance manipulations must done carefully. */ - protected void sessionDidTerminate () + protected void sessionDidEnd () { + // then let the client manager know what's up + _cmgr.clientDidEndSession(this); + // destroy the client object PresentsServer.omgr.destroyObject(_clobj.getOid()); } @@ -363,20 +336,15 @@ public class PresentsClient setConnection(null); // clear out our subscriptions. we need to do this on the dobjmgr - // thread. so we do so by posting a special event. it is important - // that we do this *after* we clear out our connection reference. - // once the connection ref is null, no more subscriptions will be - // processed (even those that were queued up before the connection - // went away) - DEvent event = new DEvent(0) - { - public boolean applyToObject (DObject target) - { + // thread. it is important that we do this *after* we clear out + // our connection reference. once the connection ref is null, no + // more subscriptions will be processed (even those that were + // queued up before the connection went away) + PresentsServer.omgr.postUnit(new Runnable() { + public void run () { clearSubscrips(); - return false; } - }; - PresentsServer.omgr.postEvent(event); + }); } /** @@ -595,15 +563,26 @@ public class PresentsClient /** * Processes logoff requests. */ - protected static class LogoffDispatcher implements MessageDispatcher + protected static class LogoffDispatcher + implements MessageDispatcher, Runnable { public void dispatch (PresentsClient client, UpstreamMessage msg) { Log.info("Client requested logoff " + "[client=" + client + "]."); - // request that the client end the session - client.endSession(); + _client = client; + // queue ourselves up to be run on the object manager thread + // where we can safely end the session + PresentsServer.omgr.postUnit(this); } + + public void run () + { + // end the session in a civilized manner + _client.endSession(); + } + + protected PresentsClient _client; } protected ClientManager _cmgr; diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java index c4c8d2fd7..77619b9a6 100644 --- a/src/java/com/threerings/presents/server/net/ConnectionManager.java +++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java @@ -1,5 +1,5 @@ // -// $Id: ConnectionManager.java,v 1.15 2002/03/05 03:19:18 mdb Exp $ +// $Id: ConnectionManager.java,v 1.16 2002/03/05 05:33:25 mdb Exp $ package com.threerings.presents.server.net; @@ -12,11 +12,15 @@ import com.samskivert.util.*; import com.threerings.presents.Log; import com.threerings.presents.client.Client; + import com.threerings.presents.io.FramingOutputStream; import com.threerings.presents.io.TypedObjectFactory; + import com.threerings.presents.net.AuthRequest; import com.threerings.presents.net.AuthResponse; import com.threerings.presents.net.DownstreamMessage; + +import com.threerings.presents.server.Authenticator; import com.threerings.presents.server.PresentsServer; /** @@ -105,6 +109,24 @@ public class ConnectionManager extends LoopingThread } } + /** + * Queues a connection up to be closed on the conmgr thread. + */ + public void closeConnection (Connection conn) + { + _deathq.append(conn); + } + + /** + * Called by the authenticator to indicate that a connection was + * successfully authenticated. + */ + public void connectionDidAuthenticate (Connection conn) + { + // slap this sucker onto the authenticated connections queue + _authq.append(conn); + } + /** * Notifies the connection observers of a connection event. Used * internally. @@ -146,6 +168,12 @@ public class ConnectionManager extends LoopingThread */ protected void iterate () { + // close any connections that have been queued up to die + Connection dconn; + while ((dconn = (Connection)_deathq.getNonBlocking()) != null) { + dconn.close(); + } + // send any messages that are waiting on the outgoing queue Tuple tup; while ((tup = (Tuple)_outq.getNonBlocking()) != null) { @@ -303,16 +331,6 @@ public class ConnectionManager extends LoopingThread notifyObservers(CONNECTION_CLOSED, conn, null, null); } - /** - * Called by the auth manager to indicate that a connection was - * successfully authenticated. - */ - void connectionDidAuthenticate (Connection conn) - { - // slap this sucker onto the authenticated connections queue - _authq.append(conn); - } - protected int _port; protected Authenticator _author; protected SelectSet _selset; @@ -320,6 +338,8 @@ public class ConnectionManager extends LoopingThread protected NonblockingServerSocket _listener; protected SelectItem _litem; + protected Queue _deathq = new Queue(); + protected Queue _outq = new Queue(); protected FramingOutputStream _framer; protected DataOutputStream _dout;