Files
narya/src/java/com/threerings/presents/server/ClientManager.java
T
Michael Bayne 9e16e87a69 Doris the refactorasaurus. Restructured the whole client object resolution
process so that it can be done by other entities than just the client
management services. Coordination between these parties is managed so that
no toes are stepped on in the course of loading and unloading clients and
everything is generally much nicer.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1086 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-03-05 05:33:25 +00:00

272 lines
9.6 KiB
Java

//
// $Id: ClientManager.java,v 1.14 2002/03/05 05:33:25 mdb Exp $
package com.threerings.presents.server;
import java.io.IOException;
import java.util.HashMap;
import com.threerings.presents.Log;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.net.AuthRequest;
import com.threerings.presents.net.AuthResponse;
import com.threerings.presents.net.Credentials;
import com.threerings.presents.server.net.*;
/**
* The client manager is responsible for managing the clients (surprise,
* surprise) which are slightly more than just connections. Clients
* persist in the absence of connections in case a user goes bye bye
* unintentionally and wants to reconnect and continue their session.
*
* <p> The client manager operates with thread safety because it is called
* both from the conmgr thread (to notify of connections showing up or
* going away) and from the dobjmgr thread (when clients are given the
* boot for application-defined reasons).
*/
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
conmgr.addConnectionObserver(this);
}
/**
* Instructs the client manager to construct instances of this derived
* class of {@link PresentsClient} to managed newly accepted client
* connections.
*/
public void setClientClass (Class clientClass)
{
// sanity check
if (!PresentsClient.class.isAssignableFrom(clientClass)) {
Log.warning("Requested to use client class that does not " +
"derive from PresentsClient " +
"[class=" + clientClass.getName() + "].");
return;
}
// make a note of it
_clientClass = clientClass;
}
/**
* Instructs the client to use instances of this {@link
* ClientResolver} derived class when resolving clients in preparation
* for starting a client session.
*/
public void setClientResolverClass (Class clrClass)
{
// sanity check
if (!ClientResolver.class.isAssignableFrom(clrClass)) {
Log.warning("Requested to use client resolver class that does " +
"not derive from ClientResolver " +
"[class=" + clrClass.getName() + "].");
} else {
// make a note of it
_clrClass = clrClass;
}
}
/**
* 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 ClientObject getClientObject (String username)
{
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
public synchronized void connectionEstablished (
Connection conn, AuthRequest req, AuthResponse rsp)
{
Credentials creds = req.getCredentials();
String username = creds.getUsername();
// see if there's a client already registered with this username
PresentsClient client = (PresentsClient)_usermap.get(username);
if (client != null) {
Log.info("Session resumed [username=" + username +
", conn=" + conn + "].");
client.resumeSession(conn);
} else {
Log.info("Session initiated [username=" + username +
", conn=" + conn + "].");
// create a new client and stick'em in the table
try {
client = (PresentsClient)_clientClass.newInstance();
client.startSession(this, username, conn);
_usermap.put(username, client);
} catch (Exception e) {
Log.warning("Failed to instantiate client instance to " +
"manage new client connection " +
"[conn=" + conn + "].");
Log.logStackTrace(e);
}
}
// map this connection to this client
_conmap.put(conn, client);
}
// documentation inherited
public synchronized
void connectionFailed (Connection conn, IOException fault)
{
// remove the client from the connection map
PresentsClient client = (PresentsClient)_conmap.remove(conn);
if (client != null) {
Log.info("Unmapped failed client [client=" + client +
", conn=" + conn + ", fault=" + fault + "].");
// let the client know the connection went away
client.wasUnmapped();
// and let the client know things went haywire
client.connectionFailed(fault);
} else {
Log.info("Unmapped connection failed? [conn=" + conn +
", fault=" + fault + "].");
}
}
// documentation inherited
public synchronized void connectionClosed (Connection conn)
{
// remove the client from the connection map
PresentsClient client = (PresentsClient)_conmap.remove(conn);
if (client != null) {
Log.info("Unmapped client [client=" + client +
", conn=" + conn + "].");
// let the client know the connection went away
client.wasUnmapped();
} else {
Log.info("Closed unmapped connection? [conn=" + conn + "].");
}
}
/**
* Called by the client instance when the client requests a logoff.
* This is called from the conmgr thread.
*/
synchronized void clientDidEndSession (PresentsClient client)
{
String username = client.getUsername();
// remove the client from the username map
PresentsClient rc = (PresentsClient)_usermap.remove(username);
// and the client object mapping as well
_objmap.remove(username);
// 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;
/** The client resolver class in use. */
protected Class _clrClass = ClientResolver.class;
}