d55ce27b2b
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@71 542714f4-19e9-0310-aa3c-eee0fc999fb1
356 lines
12 KiB
Java
356 lines
12 KiB
Java
//
|
|
// $Id: PresentsClient.java,v 1.7 2001/07/19 07:48:25 mdb Exp $
|
|
|
|
package com.threerings.cocktail.cher.server;
|
|
|
|
import java.io.IOException;
|
|
import java.util.HashMap;
|
|
|
|
import com.threerings.cocktail.cher.Log;
|
|
import com.threerings.cocktail.cher.data.ClientObject;
|
|
import com.threerings.cocktail.cher.dobj.*;
|
|
import com.threerings.cocktail.cher.net.*;
|
|
import com.threerings.cocktail.cher.server.net.*;
|
|
|
|
/**
|
|
* A client object represents a client session in the server. It is
|
|
* associated with a connection instance (while the client is connected)
|
|
* and acts as the intermediary for the remote client in terms of passing
|
|
* along events forwarded by the client, ensuring that subscriptions are
|
|
* maintained on behalf of the client and that events are forwarded to the
|
|
* client.
|
|
*
|
|
* <p><em>A note on synchronization:</em> the client object is structured
|
|
* so that its <code>Subscriber</code> implementation (which is called
|
|
* from the dobjmgr thread) can proceed without synchronization. This does
|
|
* not overlap with its other client duties which are called from the
|
|
* conmgr thread and therefore also need not be synchronized.
|
|
*/
|
|
public class Client implements Subscriber, MessageHandler
|
|
{
|
|
/**
|
|
* Constructs a new client instance bound to the specified username
|
|
* and initially associated with the specified connection instance.
|
|
*/
|
|
public Client (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)
|
|
{
|
|
_clobj = (ClientObject)object;
|
|
sendBootstrap();
|
|
}
|
|
|
|
public void requestFailed (int oid, ObjectAccessException cause)
|
|
{
|
|
Log.warning("Unable to create client object " +
|
|
"[client=" + Client.this +
|
|
", error=" + cause + "].");
|
|
}
|
|
|
|
public boolean handleEvent (DEvent event, DObject target)
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
CherServer.omgr.createObject(ClientObject.class, sub, false);
|
|
}
|
|
|
|
/**
|
|
* Returns the username with which this client instance is associated.
|
|
*/
|
|
public String getUsername ()
|
|
{
|
|
return _username;
|
|
}
|
|
|
|
/**
|
|
* Called by the client manager when a new connection arrives that
|
|
* authenticates as this already established client. This must only be
|
|
* called from the congmr thread.
|
|
*/
|
|
public void resumeSession (Connection conn)
|
|
{
|
|
Connection oldconn = getConnection();
|
|
|
|
// check to see if we've already got a connection object, in which
|
|
// case it's probably stale
|
|
if (oldconn != null) {
|
|
Log.info("Closing stale connection [old=" + oldconn +
|
|
", new=" + conn + "].");
|
|
// close the old connection (which results in everything being
|
|
// properly unregistered)
|
|
oldconn.close();
|
|
}
|
|
|
|
// start using the new connection
|
|
setConnection(conn);
|
|
|
|
// send off a bootstrap notification immediately because we've
|
|
// already got our client object
|
|
sendBootstrap();
|
|
|
|
Log.info("Session resumed [client=" + this + "].");
|
|
}
|
|
|
|
/**
|
|
* This is called once we have a handle on the client distributed
|
|
* object. It sends a bootstrap notification to the client with all
|
|
* the information it will need to interact with the server.
|
|
*/
|
|
protected void sendBootstrap ()
|
|
{
|
|
Log.info("Sending bootstrap [client=" + this + "].");
|
|
|
|
// create and populate our bootstrap data
|
|
BootstrapData data = createBootstrapData();
|
|
populateBootstrapData(data);
|
|
|
|
// create a send bootstrap notification
|
|
_conn.postMessage(new BootstrapNotification(data));
|
|
}
|
|
|
|
/**
|
|
* Derived client classes can override this member to create derived
|
|
* bootstrap data classes that contain extra bootstrap information, if
|
|
* desired.
|
|
*/
|
|
protected BootstrapData createBootstrapData ()
|
|
{
|
|
return new BootstrapData();
|
|
}
|
|
|
|
/**
|
|
* Derived client classes can override this member to populate the
|
|
* bootstrap data with additional information. They should be sure to
|
|
* call <code>super.populateBootstrapData()</code> before doing their
|
|
* own populating, however.
|
|
*/
|
|
protected void populateBootstrapData (BootstrapData data)
|
|
{
|
|
// give them the client object id
|
|
data.clientOid = _clobj.getOid();
|
|
|
|
// give them the invocation oid
|
|
data.invOid = CherServer.invmgr.getOid();
|
|
}
|
|
|
|
/**
|
|
* Called by the connection manager when this client's connection
|
|
* fails. This is invoked on the conmgr thread, and should behave
|
|
* accordingly.
|
|
*/
|
|
public void connectionFailed (IOException fault)
|
|
{
|
|
// clear out our connection reference
|
|
setConnection(null);
|
|
}
|
|
|
|
protected synchronized void setConnection (Connection conn)
|
|
{
|
|
// keep a handle to the new connection
|
|
_conn = conn;
|
|
|
|
// tell the connection to pass messages on to us (if we're setting
|
|
// a connection rather than clearing one out)
|
|
if (_conn != null) {
|
|
_conn.setMessageHandler(this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The connection instance must be accessed via this member function
|
|
* because it is read from both the dobjmgr and conmgr threads and is
|
|
* modified by the conmgr thread.
|
|
*
|
|
* @return The connection instance associated with this client or null
|
|
* if the client is not currently connected.
|
|
*/
|
|
protected synchronized Connection getConnection ()
|
|
{
|
|
return _conn;
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void handleMessage (UpstreamMessage message)
|
|
{
|
|
// we dispatch to a message dispatcher that is specialized for the
|
|
// particular class of message that we received
|
|
MessageDispatcher disp = (MessageDispatcher)
|
|
_disps.get(message.getClass());
|
|
if (disp == null) {
|
|
Log.warning("No dispacther for message [msg=" + message + "].");
|
|
return;
|
|
}
|
|
|
|
// otherwise pass the message on to the dispatcher
|
|
disp.dispatch(this, message);
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void objectAvailable (DObject object)
|
|
{
|
|
// queue up an object response
|
|
Connection conn = getConnection();
|
|
if (conn != null) {
|
|
conn.postMessage(new ObjectResponse(object));
|
|
} else {
|
|
Log.info("Dropped object available notification " +
|
|
"[client=" + this + ", oid=" + object.getOid() + "].");
|
|
}
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public void requestFailed (int oid, ObjectAccessException cause)
|
|
{
|
|
Connection conn = getConnection();
|
|
if (conn != null) {
|
|
conn.postMessage(new FailureResponse(oid));
|
|
} else {
|
|
Log.info("Dropped failure notification " +
|
|
"[client=" + this + ", oid=" + oid +
|
|
", cause=" + cause + "].");
|
|
}
|
|
}
|
|
|
|
// documentation inherited from interface
|
|
public boolean handleEvent (DEvent event, DObject target)
|
|
{
|
|
// forward the event to the client
|
|
Connection conn = getConnection();
|
|
if (conn != null) {
|
|
// only typed events will be forwarded to the client, so we
|
|
// need not worry that a non-typed event would make it here
|
|
TypedEvent tevent = (TypedEvent)event;
|
|
conn.postMessage(new EventNotification(tevent));
|
|
} else {
|
|
Log.info("Dropped event forward notification " +
|
|
"[client=" + this + ", event=" + event + "].");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Message dispatchers are used to dispatch each different type of
|
|
* upstream message. We can look the dispatcher up in a table and
|
|
* invoke it through an overloaded member which is faster (so we
|
|
* think) than doing a bunch of instanceofs.
|
|
*/
|
|
protected static interface MessageDispatcher
|
|
{
|
|
/**
|
|
* Dispatch the supplied message for the specified client.
|
|
*/
|
|
public void dispatch (Client client, UpstreamMessage mge);
|
|
}
|
|
|
|
/**
|
|
* Processes subscribe requests.
|
|
*/
|
|
protected static class SubscribeDispatcher implements MessageDispatcher
|
|
{
|
|
public void dispatch (Client client, UpstreamMessage msg)
|
|
{
|
|
SubscribeRequest req = (SubscribeRequest)msg;
|
|
Log.info("Subscribing [client=" + client +
|
|
", oid=" + req.getOid() + "].");
|
|
// forward the subscribe request to the omgr for processing
|
|
CherServer.omgr.subscribeToObject(req.getOid(), client);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes unsubscribe requests.
|
|
*/
|
|
protected static class UnsubscribeDispatcher implements MessageDispatcher
|
|
{
|
|
public void dispatch (Client client, UpstreamMessage msg)
|
|
{
|
|
UnsubscribeRequest req = (UnsubscribeRequest)msg;
|
|
Log.info("Unsubscribing [client=" + client +
|
|
", oid=" + req.getOid() + "].");
|
|
// forward the unsubscribe request to the omgr for processing
|
|
CherServer.omgr.unsubscribeFromObject(req.getOid(), client);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes forward event requests.
|
|
*/
|
|
protected static class ForwardEventDispatcher implements MessageDispatcher
|
|
{
|
|
public void dispatch (Client client, UpstreamMessage msg)
|
|
{
|
|
ForwardEventRequest req = (ForwardEventRequest)msg;
|
|
Log.info("Forwarding event [client=" + client +
|
|
", event=" + req.getEvent() + "].");
|
|
// forward the event to the omgr for processing
|
|
CherServer.omgr.postEvent(req.getEvent());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes ping requests.
|
|
*/
|
|
protected static class PingDispatcher implements MessageDispatcher
|
|
{
|
|
public void dispatch (Client client, UpstreamMessage msg)
|
|
{
|
|
Log.info("Received client ping [client=" + client + "].");
|
|
// send a pong response
|
|
Connection conn = client.getConnection();
|
|
if (conn != null) {
|
|
conn.postMessage(new PongResponse());
|
|
} else {
|
|
Log.info("Dropped pong response [client=" + client + "].");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processes logoff requests.
|
|
*/
|
|
protected static class LogoffDispatcher implements MessageDispatcher
|
|
{
|
|
public void dispatch (Client client, UpstreamMessage msg)
|
|
{
|
|
Log.info("Client requested logoff " +
|
|
"[client=" + client + "].");
|
|
// close our connection (which results in everything being
|
|
// properly unregistered)
|
|
Connection conn = client.getConnection();
|
|
if (conn != null) {
|
|
conn.close();
|
|
} else {
|
|
Log.info("Unable to close connection for logoff request " +
|
|
"[client=" + client + "].");
|
|
}
|
|
// then let the client manager know what's up
|
|
client._cmgr.clientDidEndSession(client);
|
|
}
|
|
}
|
|
|
|
protected ClientManager _cmgr;
|
|
protected String _username;
|
|
protected Connection _conn;
|
|
protected ClientObject _clobj;
|
|
|
|
protected static HashMap _disps = new HashMap();
|
|
|
|
// register our message dispatchers
|
|
static {
|
|
_disps.put(SubscribeRequest.class, new SubscribeDispatcher());
|
|
_disps.put(UnsubscribeRequest.class, new UnsubscribeDispatcher());
|
|
_disps.put(ForwardEventRequest.class, new ForwardEventDispatcher());
|
|
_disps.put(PingRequest.class, new PingDispatcher());
|
|
_disps.put(LogoffRequest.class, new LogoffDispatcher());
|
|
}
|
|
}
|