From ba4985053fe46a025ee438b971e07a7d91fde15f Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 19 Sep 2002 23:36:59 +0000 Subject: [PATCH] Revamped "persona selection" support. Things now actually work, and in as sensible a way as we can hope for. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1717 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../threerings/crowd/server/CrowdClient.java | 12 +- .../threerings/micasa/client/LogonPanel.java | 8 +- .../threerings/presents/client/Client.java | 73 ++++++++--- .../presents/client/ClientAdapter.java | 7 +- .../presents/client/ClientObserver.java | 10 +- .../presents/client/InvocationDirector.java | 84 ++++++++++--- .../presents/data/ClientObject.dobj | 6 +- .../presents/data/ClientObject.java | 6 +- .../presents/server/ClientManager.java | 112 +++++++---------- .../presents/server/PresentsClient.java | 118 ++++++++++++++++-- 10 files changed, 301 insertions(+), 135 deletions(-) diff --git a/src/java/com/threerings/crowd/server/CrowdClient.java b/src/java/com/threerings/crowd/server/CrowdClient.java index 6b4ac7fa1..346ef44b0 100644 --- a/src/java/com/threerings/crowd/server/CrowdClient.java +++ b/src/java/com/threerings/crowd/server/CrowdClient.java @@ -1,12 +1,10 @@ // -// $Id: CrowdClient.java,v 1.9 2002/09/17 01:23:09 mdb Exp $ +// $Id: CrowdClient.java,v 1.10 2002/09/19 23:36:59 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 but doesn't really do * anything at present. It exists mainly so that implementation systems @@ -15,12 +13,4 @@ import com.threerings.crowd.data.BodyObject; */ public class CrowdClient extends PresentsClient { - // documentation inherited - public void setUsername (String username) - { - super.setUsername(username); - - // update the username in our body object - ((BodyObject)_clobj).setUsername(username); - } } diff --git a/src/java/com/threerings/micasa/client/LogonPanel.java b/src/java/com/threerings/micasa/client/LogonPanel.java index f6c77e3a1..e7bed758b 100644 --- a/src/java/com/threerings/micasa/client/LogonPanel.java +++ b/src/java/com/threerings/micasa/client/LogonPanel.java @@ -1,5 +1,5 @@ // -// $Id: LogonPanel.java,v 1.7 2002/07/17 18:11:02 mdb Exp $ +// $Id: LogonPanel.java,v 1.8 2002/09/19 23:36:59 mdb Exp $ package com.threerings.micasa.client; @@ -156,6 +156,12 @@ public class LogonPanel extends JPanel setLogonEnabled(true); } + // documentation inherited from interface + public void clientObjectDidChange (Client client) + { + // nothing we can do here... + } + // documentation inherited from interface public void clientConnectionFailed (Client client, Exception cause) { diff --git a/src/java/com/threerings/presents/client/Client.java b/src/java/com/threerings/presents/client/Client.java index 865a45499..06ce662d3 100644 --- a/src/java/com/threerings/presents/client/Client.java +++ b/src/java/com/threerings/presents/client/Client.java @@ -1,5 +1,5 @@ // -// $Id: Client.java,v 1.28 2002/08/14 19:07:54 mdb Exp $ +// $Id: Client.java,v 1.29 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.client; @@ -333,20 +333,7 @@ public class Client protected void clockDeltaEstablished () { // initialize our invocation director - ResultListener rl = new ResultListener() { - public void requestCompleted (Object result) { - // keep this around - _clobj = (ClientObject)result; - // let the client know that logon has now fully succeeded - notifyObservers(Client.CLIENT_DID_LOGON, null); - } - - public void requestFailed (Exception cause) { - // pass the buck onto the listeners - notifyObservers(Client.CLIENT_FAILED_TO_LOGON, cause); - } - }; - _invdir.init(_comm.getDObjectManager(), _cloid, rl); + _invdir.init(_comm.getDObjectManager(), _cloid, this); // we can't quite call initialization completed at this point // because we need for the invocation director to fully initialize @@ -354,6 +341,42 @@ public class Client // client loose to do things like request invocation services } + /** + * Called by the invocation director when it successfully subscribes + * to the client object immediately following logon. + */ + protected void gotClientObject (ClientObject clobj) + { + // keep this around + _clobj = clobj; + + // let the client know that logon has now fully succeeded + notifyObservers(Client.CLIENT_DID_LOGON, null); + } + + /** + * Called by the invocation director if it fails to subscribe to the + * client object after logon. + */ + protected void getClientObjectFailed (Exception cause) + { + // pass the buck onto the listeners + notifyObservers(Client.CLIENT_FAILED_TO_LOGON, cause); + } + + /** + * Called by the invocation director when it discovers that the client + * object has changed. + */ + protected void clientObjectDidChange (ClientObject clobj) + { + // slip the new client object into the business + _clobj = clobj; + + // report to our observers + notifyObservers(Client.CLIENT_OBJECT_CHANGED, null); + } + boolean notifyObservers (int code, Exception cause) { final Notifier noty = new Notifier(code, cause); @@ -365,8 +388,11 @@ public class Client } }; - // we need to run immediately if this is WILL_LOGOFF - if (code == CLIENT_WILL_LOGOFF) { + // we need to run immediately if this is WILL_LOGOFF or if we have + // no invoker (which currently only happens in some really obscure + // circumstances where we're using a Client instance on the server + // so that we can sort of pretend to be a real client) + if (code == CLIENT_WILL_LOGOFF || _invoker == null) { unit.run(); return noty.getRejected(); @@ -475,6 +501,12 @@ public class Client } break; + case CLIENT_OBJECT_CHANGED: + if (obs instanceof ClientObserver) { + ((ClientObserver)obs).clientObjectDidChange(Client.this); + } + break; + case CLIENT_CONNECTION_FAILED: if (obs instanceof ClientObserver) { ((ClientObserver)obs).clientConnectionFailed( @@ -554,7 +586,8 @@ public class Client // client observer codes static final int CLIENT_DID_LOGON = 0; static final int CLIENT_FAILED_TO_LOGON = 1; - static final int CLIENT_CONNECTION_FAILED = 2; - static final int CLIENT_WILL_LOGOFF = 3; - static final int CLIENT_DID_LOGOFF = 4; + static final int CLIENT_OBJECT_CHANGED = 2; + static final int CLIENT_CONNECTION_FAILED = 3; + static final int CLIENT_WILL_LOGOFF = 4; + static final int CLIENT_DID_LOGOFF = 5; } diff --git a/src/java/com/threerings/presents/client/ClientAdapter.java b/src/java/com/threerings/presents/client/ClientAdapter.java index 78d1d36f1..ae7eff31f 100644 --- a/src/java/com/threerings/presents/client/ClientAdapter.java +++ b/src/java/com/threerings/presents/client/ClientAdapter.java @@ -1,5 +1,5 @@ // -// $Id: ClientAdapter.java,v 1.3 2001/10/11 04:07:52 mdb Exp $ +// $Id: ClientAdapter.java,v 1.4 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.client; @@ -24,6 +24,11 @@ public class ClientAdapter implements ClientObserver { } + // documentation inherited from interface + public void clientObjectDidChange (Client client) + { + } + // documentation inherited public void clientConnectionFailed (Client client, Exception cause) { diff --git a/src/java/com/threerings/presents/client/ClientObserver.java b/src/java/com/threerings/presents/client/ClientObserver.java index 485d78d7d..17bc66dbc 100644 --- a/src/java/com/threerings/presents/client/ClientObserver.java +++ b/src/java/com/threerings/presents/client/ClientObserver.java @@ -1,5 +1,5 @@ // -// $Id: ClientObserver.java,v 1.5 2002/05/16 18:17:27 shaper Exp $ +// $Id: ClientObserver.java,v 1.6 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.client; @@ -43,6 +43,14 @@ public interface ClientObserver extends SessionObserver */ public void clientFailedToLogon (Client client, Exception cause); + /** + * For systems that allow switching screen names after logon, this + * method is called whenever a screen name change takes place to + * report that the client object has been replaced to potential + * client-side subscribers. + */ + public void clientObjectDidChange (Client client); + /** * Called when the connection to the server went away for some * unexpected reason. This will be followed by a call to diff --git a/src/java/com/threerings/presents/client/InvocationDirector.java b/src/java/com/threerings/presents/client/InvocationDirector.java index 1fdabe07c..5f7ce808f 100644 --- a/src/java/com/threerings/presents/client/InvocationDirector.java +++ b/src/java/com/threerings/presents/client/InvocationDirector.java @@ -1,5 +1,5 @@ // -// $Id: InvocationDirector.java,v 1.21 2002/08/14 19:07:54 mdb Exp $ +// $Id: InvocationDirector.java,v 1.22 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.client; @@ -20,10 +20,12 @@ 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.DSet; 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.MessageEvent; import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.Subscriber; @@ -41,25 +43,22 @@ public class InvocationDirector * 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 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. + * @param client a reference to the client for whom we're doing our + * business. */ - public void init (DObjectManager omgr, final int cloid, - final ResultListener initListener) + public void init (DObjectManager omgr, final int cloid, Client client) { - // keep this for later + // keep these for later _omgr = omgr; + _client = client; // add ourselves as a subscriber to the client object _omgr.subscribeToObject(cloid, new Subscriber() { - public void objectAvailable (DObject object) - { + public void objectAvailable (DObject object) { // keep a handle on this bad boy _clobj = (ClientObject)object; - // add ourselves as a message listener + // add ourselves as an event listener _clobj.addListener(InvocationDirector.this); // assign a mapping to already registered receivers @@ -67,16 +66,16 @@ public class InvocationDirector // let the client know that we're ready to go now that // we've got our subscription to the client object - initListener.requestCompleted(object); + _client.gotClientObject(_clobj); } - public void requestFailed (int oid, ObjectAccessException cause) - { + public void requestFailed (int oid, ObjectAccessException cause) { // aiya! we were unable to subscribe to the client object. // we're hosed, hosed, hosed Log.warning("Invocation director unable to subscribe to " + - "client object [cloid=" + cloid + "]!"); - initListener.requestFailed(cause); + "client object [cloid=" + cloid + + ", cause=" + cause + "]!"); + _client.getClientObjectFailed(cause); } }); } @@ -215,6 +214,13 @@ public class InvocationDirector (InvocationNotificationEvent)event; handleInvocationNotification( ine.getReceiverId(), ine.getMethodId(), ine.getArgs()); + + } else if (event instanceof MessageEvent) { + MessageEvent mevt = (MessageEvent)event; + if (mevt.getName().equals(ClientObject.CLOBJ_CHANGED)) { + handleClientObjectChanged( + ((Integer)mevt.getArgs()[0]).intValue()); + } } } @@ -283,6 +289,49 @@ public class InvocationDirector } /** + * Called when the server has informed us that our previous client + * object is going the way of the Dodo because we're changing screen + * names. We subscribe to the new object and report to the client once + * we've got our hands on it. + */ + protected void handleClientObjectChanged (int newCloid) + { + // subscribe to the new client object + _omgr.subscribeToObject(newCloid, new Subscriber() { + public void objectAvailable (DObject object) { + // grab a reference to our old receiver registrations + DSet receivers = _clobj.receivers; + + // replace the client object + _clobj = (ClientObject)object; + + // add ourselves as an event listener + _clobj.addListener(InvocationDirector.this); + + // reregister our receivers + try { + _clobj.startTransaction(); + Iterator iter = receivers.entries(); + while (iter.hasNext()) { + _clobj.addToReceivers((Registration)iter.next()); + } + } finally { + _clobj.commitTransaction(); + } + + // and report the switcheroo back to the client + _client.clientObjectDidChange(_clobj); + } + + public void requestFailed (int oid, ObjectAccessException cause) { + Log.warning("Aiya! Unable to subscribe to changed " + + "client object [cloid=" + oid + + ", cause=" + cause + "]."); + } + }); + } + + /** * Used to generate monotonically increasing invocation request ids. */ protected synchronized short nextRequestId () @@ -301,6 +350,9 @@ public class InvocationDirector /** The distributed object manager with which we interact. */ protected DObjectManager _omgr; + /** The client for whom we're working. */ + protected Client _client; + /** Our client object; invocation responses and notifications are * received on this object. */ protected ClientObject _clobj; diff --git a/src/java/com/threerings/presents/data/ClientObject.dobj b/src/java/com/threerings/presents/data/ClientObject.dobj index b124e5252..a022ba8c2 100644 --- a/src/java/com/threerings/presents/data/ClientObject.dobj +++ b/src/java/com/threerings/presents/data/ClientObject.dobj @@ -1,5 +1,5 @@ // -// $Id: ClientObject.dobj,v 1.1 2002/08/14 19:07:54 mdb Exp $ +// $Id: ClientObject.dobj,v 1.2 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.data; @@ -14,6 +14,10 @@ import com.threerings.presents.dobj.DSet; */ public class ClientObject extends DObject { + /** The name of a message event delivered to the client when they + * switch usernames (and therefore user objects). */ + public static final String CLOBJ_CHANGED = "!clobj_changed!"; + /** Used to publish all invocation service receivers registered on * this client. */ public DSet receivers = new DSet(); diff --git a/src/java/com/threerings/presents/data/ClientObject.java b/src/java/com/threerings/presents/data/ClientObject.java index 366bfde18..69f1ea81d 100644 --- a/src/java/com/threerings/presents/data/ClientObject.java +++ b/src/java/com/threerings/presents/data/ClientObject.java @@ -1,5 +1,5 @@ // -// $Id: ClientObject.java,v 1.3 2002/08/14 19:07:54 mdb Exp $ +// $Id: ClientObject.java,v 1.4 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.data; @@ -17,6 +17,10 @@ public class ClientObject extends DObject /** The field name of the receivers field. */ public static final String RECEIVERS = "receivers"; + /** The name of a message event delivered to the client when they + * switch usernames (and therefore user objects). */ + public static final String CLOBJ_CHANGED = "!clobj_changed!"; + /** Used to publish all invocation service receivers registered on * this client. */ public DSet receivers = new DSet(); diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java index 4460135d1..49a2d02e2 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.22 2002/09/16 23:34:25 mdb Exp $ +// $Id: ClientManager.java,v 1.23 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.server; @@ -86,12 +86,12 @@ public class ClientManager implements ConnectionObserver /** * Returns the client instance that manages the client session for the - * specified username or null if that client is not currently + * specified credentials or null if that client is not currently * connected to the server. */ - public PresentsClient getClient (String username) + public PresentsClient getClient (Credentials creds) { - return (PresentsClient)_usermap.get(username); + return (PresentsClient)_usermap.get(creds); } /** @@ -101,7 +101,17 @@ public class ClientManager implements ConnectionObserver */ public ClientObject getClientObject (String username) { - return (ClientObject)_objmap.get(username); + return (ClientObject)_objmap.get(toKey(username)); + } + + /** + * We convert usernames to lower case in the username to client object + * mapping so that we can pass arbitrarily cased usernames (like those + * that might be typed in by a "user") straight on through. + */ + protected final String toKey (String username) + { + return username.toLowerCase(); } /** @@ -115,14 +125,15 @@ public class ClientManager implements ConnectionObserver String username, ClientResolutionListener listener) { // look to see if the client object is already resolved - ClientObject clobj = (ClientObject)_objmap.get(username); + String key = toKey(username); + ClientObject clobj = (ClientObject)_objmap.get(key); if (clobj != null) { listener.clientResolved(username, clobj); return; } // look to see if it's currently being resolved - ClientResolver clr = (ClientResolver)_penders.get(username); + ClientResolver clr = (ClientResolver)_penders.get(key); if (clr != null) { // throw this guy onto the bandwagon clr.addResolutionListener(listener); @@ -154,10 +165,11 @@ public class ClientManager implements ConnectionObserver String username, ClientObject clobj) { // stuff the object into the mapping table - _objmap.put(username, clobj); + String key = toKey(username); + _objmap.put(key, clobj); // and remove the resolution listener - _penders.remove(username); + _penders.remove(key); } /** @@ -172,11 +184,12 @@ public class ClientManager implements ConnectionObserver { // we only remove the mapping if there's not a session in progress // (which is indicated by an entry in the locks table) - if (_locks.contains(username)) { + String key = toKey(username); + if (_locks.contains(key)) { return; } - ClientObject clobj = (ClientObject)_objmap.remove(username); + ClientObject clobj = (ClientObject)_objmap.remove(key); if (clobj != null) { PresentsServer.omgr.destroyObject(clobj.getOid()); } else { @@ -185,45 +198,6 @@ public class ClientManager implements ConnectionObserver } } - /** - * Remaps a client from its old username to the specified new - * username. The client must end its session using the new username. - * This most likely shouldn't be called anywhere except from {@link - * PresentsClient#setUsername}. - * - * @return true if the remapping succeeded, false if it failed. - */ - protected synchronized boolean remapClient ( - String oldname, String newname) - { - // make sure they are already mapped - PresentsClient client = (PresentsClient)_usermap.remove(oldname); - if (client == null) { - Log.warning("Aiya! Can't remap non-existent user " + - "[oldname=" + oldname + ", newname=" + newname + "]."); - return false; - } - - // map them under their new name - _usermap.put(newname, client); - - // release their old lock and create a lock for their new name - releaseClient(oldname); - lockClient(newname); - - // update their client object mapping - ClientObject clobj = (ClientObject)_objmap.remove(oldname); - if (clobj == null) { - Log.warning("Aiya! Unable to unmap old client object when " + - "remapping user [oldname=" + oldname + - ", newname=" + newname + "]. Hoping for the best."); - } else { - _objmap.put(newname, clobj); - } - - return true; - } - /** * When a client object becomes part of an active session, this method * should be called to ensure that it is not unloaded by any entities @@ -231,30 +205,31 @@ public class ClientManager implements ConnectionObserver * automatically when a real user starts a session by establishing a * network connection with the server. If a client session is managed * via some other mechanism (bots managed by the server, for example), - * this method and its corresponding {@link #releaseClient} should be - * called at the beginning and end of the faked client session - * respectively. + * this method and its corresponding {@link #releaseClientObject} + * should be called at the beginning and end of the faked client + * session respectively. */ - public synchronized void lockClient (String username) + public synchronized void lockClientObject (String username) { - if (_locks.contains(username)) { + String key = toKey(username); + if (_locks.contains(key)) { Log.warning("Requested to lock already locked user " + "[username=" + username + "]."); Thread.dumpStack(); } else { - _locks.add(username); + _locks.add(key); } } /** * Releases a client object when their session has ended. * - * @see #lockClient + * @see #lockClientObject */ - public synchronized void releaseClient (String username) + public synchronized void releaseClientObject (String username) { - if (!_locks.remove(username)) { + if (!_locks.remove(toKey(username))) { Log.warning("Requested to unlock a user that was not locked " + "[username=" + username + "]."); Thread.dumpStack(); @@ -268,8 +243,8 @@ public class ClientManager implements ConnectionObserver 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); + // see if a client is already registered with these credentials + PresentsClient client = (PresentsClient)_usermap.get(creds); if (client != null) { Log.info("Session resumed [username=" + username + @@ -283,13 +258,10 @@ public class ClientManager implements ConnectionObserver try { // create a client and start up its session client = (PresentsClient)_clientClass.newInstance(); - client.startSession(this, username, conn); - - // lock this client for the duration of this session - lockClient(username); + client.startSession(this, creds, conn); // map their client instance - _usermap.put(username, client); + _usermap.put(creds, client); } catch (Exception e) { Log.warning("Failed to instantiate client instance to " + @@ -350,9 +322,11 @@ public class ClientManager implements ConnectionObserver */ synchronized void clientDidEndSession (PresentsClient client) { + Credentials creds = client.getCredentials(); String username = client.getUsername(); + // remove the client from the username map - PresentsClient rc = (PresentsClient)_usermap.remove(username); + PresentsClient rc = (PresentsClient)_usermap.remove(creds); // sanity check just because we can if (rc == null) { @@ -372,9 +346,7 @@ public class ClientManager implements ConnectionObserver Log.info("Ending session [client=" + client + "]."); } - // release the client session - releaseClient(username); - // and unmap (and destroy) their client object + // unmap (and destroy) the client object unmapClientObject(username); } diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java index 1aee7da4f..bd5413937 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.37 2002/09/18 01:10:27 shaper Exp $ +// $Id: PresentsClient.java,v 1.38 2002/09/19 23:36:59 mdb Exp $ package com.threerings.presents.server; @@ -20,6 +20,7 @@ import com.threerings.presents.dobj.Subscriber; import com.threerings.presents.net.BootstrapData; import com.threerings.presents.net.BootstrapNotification; +import com.threerings.presents.net.Credentials; import com.threerings.presents.net.EventNotification; import com.threerings.presents.net.UpstreamMessage; @@ -54,6 +55,32 @@ public class PresentsClient implements Subscriber, EventListener, MessageHandler, ClientResolutionListener { + /** Used by {@link #setUsername} to report success or failure. */ + public static interface UserChangeListener + { + /** Called when the new client object has been resolved and the + * new client object reported to the client, but the old one has + * not yet been destroyed. Any events delivered on this callback + * to the old client object will be delivered. */ + public void changeReported (ClientObject newObj); + + /** Called when the user change is completed, the old client + * object is destroyed and all updates are committed. */ + public void changeCompleted (ClientObject newObj); + + /** Called if some failure occurs during the user change + * process. */ + public void changeFailed (Exception cause); + } + + /** + * Returns the credentials used to authenticate this client. + */ + public Credentials getCredentials () + { + return _creds; + } + /** * Returns the username with which this client instance is associated. */ @@ -79,17 +106,58 @@ public class PresentsClient * services when the user logs on, but anything else that has had its * grubby mits on the username will be left to its own devices, hence * the care that must be exercised when using this method. + * + * @param ucl an entity that will (optionally) be notified when the + * username conversion process is complete. */ - public void setUsername (String username) + public void setUsername (String username, final UserChangeListener ucl) { - // remap the client object in the client manager - if (_cmgr.remapClient(_username, username)) { - // change our internal business - _username = username; - } else { - Log.warning("Unable to remap username [client=" + this + - ", newname=" + username + "]."); - } + ClientResolutionListener clr = new ClientResolutionListener() { + public void clientResolved (String username, ClientObject clobj) { + // let the client know that the rug has been yanked out + // from under their ass + Object[] args = new Object[] { new Integer(clobj.getOid()) }; + _clobj.postMessage(ClientObject.CLOBJ_CHANGED, args); + + // let the caller know that we've got some new business + if (ucl != null) { + ucl.changeReported(clobj); + } + + // release our old client object + _cmgr.releaseClientObject(_username); + + // unmap the old client object; this will destroy it and + // clear out our username mapping + _cmgr.unmapClientObject(_username); + + // update our internal fields + _username = username; + _clobj = clobj; + + // lock our new client object + _cmgr.lockClientObject(_username); + + // let our listener know we're groovy + if (ucl != null) { + ucl.changeCompleted(_clobj); + } + } + + public void resolutionFailed (String username, Exception reason) { + Log.warning("Unable to resolve new client object " + + "[oldname=" + _username + ", newname=" + username + + ", reason=" + reason + "]."); + + // let our listener know we're hosed + if (ucl != null) { + ucl.changeFailed(reason); + } + } + }; + + // resolve the new client object + _cmgr.resolveClientObject(username, clr); } /** @@ -105,14 +173,34 @@ public class PresentsClient * connection instance and client object and begins a client session. */ protected void startSession ( - ClientManager cmgr, String username, Connection conn) + ClientManager cmgr, Credentials creds, Connection conn) { _cmgr = cmgr; - _username = username; + _creds = creds; setConnection(conn); + // obtain our starting username + assignStartingUsername(); + + // obtain a lock for our username->client object mapping while we + // have an active session + cmgr.lockClientObject(_username); + // resolve our client object before we get fully underway - cmgr.resolveClientObject(username, this); + cmgr.resolveClientObject(_username, this); + } + + /** + * This is factored out to allow derived classes to use a different + * starting username than the one supplied in the user's credentials. + * Generally one only wants to munge the starting username if the user + * will subsequently choose a "screen name" and it is desirable to + * avoid collision between the authentication user namespace and the + * screen namespace. + */ + protected void assignStartingUsername () + { + _username = _creds.getUsername(); } // documentation inherited from interface @@ -307,6 +395,9 @@ public class PresentsClient // about to generate clearSubscrips(); + // release our locked client object + _cmgr.releaseClientObject(_username); + // then let the client manager know what's up (it will take care // of destroying our client object for us) _cmgr.clientDidEndSession(this); @@ -616,6 +707,7 @@ public class PresentsClient } protected ClientManager _cmgr; + protected Credentials _creds; protected String _username; protected Connection _conn; protected ClientObject _clobj;