diff --git a/src/java/com/threerings/admin/client/ConfigObjectManager.java b/src/java/com/threerings/admin/client/ConfigObjectManager.java index 63e68c5c2..fa2b89e79 100644 --- a/src/java/com/threerings/admin/client/ConfigObjectManager.java +++ b/src/java/com/threerings/admin/client/ConfigObjectManager.java @@ -1,54 +1,56 @@ +// +// $Id$ + package com.threerings.admin.client; import java.util.HashMap; import com.samskivert.util.StringUtil; -import com.threerings.admin.Log; -import com.threerings.admin.data.ConfigObject; - import com.threerings.presents.client.Client; -import com.threerings.presents.client.SessionObserver; +import com.threerings.presents.client.ClientAdapter; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObjectManager; import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.presents.dobj.Subscriber; +import com.threerings.admin.Log; +import com.threerings.admin.data.AdminCodes; +import com.threerings.admin.data.ConfigObject; + +/** + * Handles subscribing to admin config objects. + */ public class ConfigObjectManager implements AdminService.ConfigInfoListener { - public ConfigObjectManager(Client client) { + public ConfigObjectManager(Client client) + { _serverconfig = new HashMap(); _client = client; - _client.addClientObserver(new SessionObserver() { - // documentation inherited from interface SessionObserver + _client.addClientObserver(new ClientAdapter() { + public void clientWillLogon (Client client) { + client.addServiceGroup(AdminCodes.ADMIN_GROUP); + } public void clientDidLogon (Client client) { - // Initialize the dobjmgr _dobjmgr = _client.getDObjectManager(); - // Now that we're logged on, let's grab the server configs - _service = (AdminService) - client.requireService(AdminService.class); + _service = (AdminService)client.requireService(AdminService.class); getConfigInfo(); } - - // documentation inherited from interface SessionObserver public void clientDidLogoff (Client client) { // Clean up our subscription to the server's configuration for (int ii = 0; ii < _csubscribers.length; ii++) { _csubscribers[ii].cleanup(); } } - - // documentation inherited from interface SessionObserver - public void clientObjectDidChange (Client client) {} }); } - + /** - * Convenience: generate a getConfigInfo request to the AdminService - * from the external class, instead from within the anonymous inner class + * Returns the ConfigObject identified by the given key */ - protected void getConfigInfo() { - _service.getConfigInfo(_client, this); + public ConfigObject getServerConfig (String key) + { + return _serverconfig.get(key); } // documentation inherited from interface AdminService.ConfigInfoListener @@ -60,29 +62,32 @@ public class ConfigObjectManager implements AdminService.ConfigInfoListener _csubscribers[ii].subscribeConfig(keys[ii], oids[ii]); } } - + // documentation inherited from interface AdminService.ConfigInfoListener - public void requestFailed (String reason) { + public void requestFailed (String reason) + { Log.warning("Oh bugger, we didn't get the config data: " + reason); } - + /** - * Returns the ConfigObject identified by the given key + * Convenience: generate a getConfigInfo request to the AdminService from the external class, + * instead from within the anonymous inner class */ - public ConfigObject getServerConfig (String key) { - return _serverconfig.get(key); + protected void getConfigInfo() + { + _service.getConfigInfo(_client, this); } - + /** - * This class takes care of the details of subscribing to and placing - * an individual ConfigObject that the server knows about into a HashMap + * This class takes care of the details of subscribing to and placing an individual + * ConfigObject that the server knows about into a HashMap */ protected class ConfigObjectSubscriber implements Subscriber { /** - * This method requests that we place a subscription to the - * ConfigObject with the given oid, identified by the key; when the - * object becomes available, it's added to our serverconfig map. + * This method requests that we place a subscription to the ConfigObject with the given + * oid, identified by the key; when the object becomes available, it's added to our + * serverconfig map. */ public void subscribeConfig (String key, int oid) { _key = key; @@ -95,15 +100,15 @@ public class ConfigObjectManager implements AdminService.ConfigInfoListener _cobj = object; _serverconfig.put(_key, _cobj); } - + // documentation inherited from interface Subscriber public void requestFailed (int oid, ObjectAccessException cause) { Log.warning("Unable to subscribe to config object " + _key); } - + /** - * Signals that we should stop subscribing to our ConfigObject, - * and flush out the entry from the serverconfig map. + * Signals that we should stop subscribing to our ConfigObject, and flush out the entry + * from the serverconfig map. */ public void cleanup () { // clear out our subscription @@ -114,26 +119,26 @@ public class ConfigObjectManager implements AdminService.ConfigInfoListener /** The object that we are tracking */ protected ConfigObject _cobj; - + /** The name of the config object that we are subscribing to */ protected String _key; - + /** The oid of the object that we're tracking */ protected int _oid; } - + /** An array of handlers that each subscribe to a single ConfigObject */ protected ConfigObjectSubscriber[] _csubscribers; - + /** Our local copy of the server-side runtime configuration */ protected HashMap _serverconfig; - + /** Our distributed object manager */ protected DObjectManager _dobjmgr; - + /** Our admin service that we're using to fetch data */ protected AdminService _service; - + /** Our client object */ protected Client _client; } diff --git a/src/java/com/threerings/admin/data/AdminCodes.java b/src/java/com/threerings/admin/data/AdminCodes.java new file mode 100644 index 000000000..de2a8a4af --- /dev/null +++ b/src/java/com/threerings/admin/data/AdminCodes.java @@ -0,0 +1,33 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.admin.data; + +import com.threerings.presents.data.InvocationCodes; + +/** + * Codes and consants relating to the admin services. + */ +public interface AdminCodes extends InvocationCodes +{ + /** Defines our invocation service group. */ + public static final String ADMIN_GROUP = "presents.admin"; +} diff --git a/src/java/com/threerings/admin/server/AdminProvider.java b/src/java/com/threerings/admin/server/AdminProvider.java index bbf5f507a..363091df6 100644 --- a/src/java/com/threerings/admin/server/AdminProvider.java +++ b/src/java/com/threerings/admin/server/AdminProvider.java @@ -26,36 +26,32 @@ import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.InvocationProvider; import com.threerings.admin.client.AdminService; +import com.threerings.admin.data.AdminCodes; /** - * Provides the server-side implementation of various administrator - * services. + * Provides the server-side implementation of various administrator services. */ public class AdminProvider implements InvocationProvider { /** - * Constructs an admin provider and registers it with the invocation - * manager to handle admin services. This must be called by any server - * that wishes to make use of the admin services. + * Constructs an admin provider and registers it with the invocation manager to handle admin + * services. This must be called by any server that wishes to make use of the admin services. */ public static void init (InvocationManager invmgr, ConfigRegistry registry) { invmgr.registerDispatcher( - new AdminDispatcher(new AdminProvider(registry)), true); + new AdminDispatcher(new AdminProvider(registry)), AdminCodes.ADMIN_GROUP); } /** * Handles a request for the list of config objects. */ - public void getConfigInfo ( - ClientObject caller, AdminService.ConfigInfoListener listener) + public void getConfigInfo (ClientObject caller, AdminService.ConfigInfoListener listener) { - // we don't have to validate the request because the user can't do - // anything with the keys or oids unless they're an admin (we put the - // burden of doing that checking on the creator of the config object - // because we would otherwise need some mechanism to determine whether - // a user is an admin and we don't want to force some primitive system - // on the service user) + // we don't have to validate the request because the user can't do anything with the keys + // or oids unless they're an admin (we put the burden of doing that checking on the creator + // of the config object because we would otherwise need some mechanism to determine whether + // a user is an admin and we don't want to force some primitive system on the service user) String[] keys = _registry.getKeys(); int[] oids = new int[keys.length]; for (int ii = 0; ii < keys.length; ii++) { diff --git a/src/java/com/threerings/crowd/chat/client/ChatDirector.java b/src/java/com/threerings/crowd/chat/client/ChatDirector.java index 918101726..cc81b4b7c 100644 --- a/src/java/com/threerings/crowd/chat/client/ChatDirector.java +++ b/src/java/com/threerings/crowd/chat/client/ChatDirector.java @@ -52,6 +52,7 @@ import com.threerings.util.TimeUtil; import com.threerings.crowd.Log; import com.threerings.crowd.client.LocationObserver; import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.CrowdCodes; import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.util.CrowdContext; @@ -1002,7 +1003,13 @@ public class ChatDirector extends BasicDirector return (type == null) ? PLACE_CHAT_TYPE : type; } - // documentation inherited from interface + @Override // from BasicDirector + protected void registerServices (Client client) + { + client.addServiceGroup(CrowdCodes.CROWD_GROUP); + } + + @Override // from BasicDirector protected void fetchServices (Client client) { // get a handle on our chat service diff --git a/src/java/com/threerings/crowd/chat/data/ChatCodes.java b/src/java/com/threerings/crowd/chat/data/ChatCodes.java index b2b534eda..db11ed1dc 100644 --- a/src/java/com/threerings/crowd/chat/data/ChatCodes.java +++ b/src/java/com/threerings/crowd/chat/data/ChatCodes.java @@ -33,19 +33,19 @@ import com.threerings.crowd.chat.client.SpeakService; */ public interface ChatCodes extends InvocationCodes { - /** A return value used by the ChatDirector and possibly other entities - * to indicate successful processing of chat. */ + /** A return value used by the ChatDirector and possibly other entities to indicate successful + * processing of chat. */ public static final String SUCCESS = "success"; /** The message identifier for a chat notification message. */ public static final String CHAT_NOTIFICATION = "chat"; - /** The access control identifier for normal chat privileges. See - * {@link BodyObject#checkAccess}. */ + /** The access control identifier for normal chat privileges. See {@link + * BodyObject#checkAccess}. */ public static final String CHAT_ACCESS = "crowd.chat.chat"; - /** The access control identifier for broadcast chat privileges. See - * {@link BodyObject#checkAccess}. */ + /** The access control identifier for broadcast chat privileges. See {@link + * BodyObject#checkAccess}. */ public static final String BROADCAST_ACCESS = "crowd.chat.broadcast"; /** The configuration key for idle time. */ @@ -54,10 +54,9 @@ public interface ChatCodes extends InvocationCodes /** The default time after which a player is assumed idle. */ public static final long DEFAULT_IDLE_TIME = 3 * 60 * 1000L; - /** The chat localtype code for chat messages delivered on the place - * object currently occupied by the client. This is the only type of - * chat message that will be delivered unless the chat director is - * explicitly provided with other chat message sources via {@link + /** The chat localtype code for chat messages delivered on the place object currently occupied + * by the client. This is the only type of chat message that will be delivered unless the chat + * director is explicitly provided with other chat message sources via {@link * ChatDirector#addAuxiliarySource}. */ public static final String PLACE_CHAT_TYPE = "placeChat"; @@ -67,21 +66,18 @@ public interface ChatCodes extends InvocationCodes /** The default mode used by {@link SpeakService#speak} requests. */ public static final byte DEFAULT_MODE = 0; - /** A {@link SpeakService#speak} mode to indicate that the user is - * thinking what they're saying, or is it that they're saying what - * they're thinking? */ + /** A {@link SpeakService#speak} mode to indicate that the user is thinking what they're + * saying, or is it that they're saying what they're thinking? */ public static final byte THINK_MODE = 1; - /** A {@link SpeakService#speak} mode to indicate that a speak is - * actually an emote. */ + /** A {@link SpeakService#speak} mode to indicate that a speak is actually an emote. */ public static final byte EMOTE_MODE = 2; - /** A {@link SpeakService#speak} mode to indicate that a speak is - * actually a shout. */ + /** A {@link SpeakService#speak} mode to indicate that a speak is actually a shout. */ public static final byte SHOUT_MODE = 3; - /** A {@link SpeakService#speak} mode to indicate that a speak is - * actually a server-wide broadcast. */ + /** A {@link SpeakService#speak} mode to indicate that a speak is actually a server-wide + * broadcast. */ public static final byte BROADCAST_MODE = 4; /** String translations for the various chat modes. */ @@ -89,11 +85,9 @@ public interface ChatCodes extends InvocationCodes "default", "think", "emote", "shout", "broadcast" }; - /** An error code delivered when the user targeted for a tell - * notification is not online. */ + /** An error code delivered when the user targeted for a tell notification is not online. */ public static final String USER_NOT_ONLINE = "m.user_not_online"; - /** An error code delivered when the user targeted for a tell - * notification is disconnected. */ + /** An error code delivered when the user targeted for a tell notification is disconnected. */ public static final String USER_DISCONNECTED = "m.user_disconnected"; } diff --git a/src/java/com/threerings/crowd/chat/server/ChatProvider.java b/src/java/com/threerings/crowd/chat/server/ChatProvider.java index 45b65eba8..97a2adc2d 100644 --- a/src/java/com/threerings/crowd/chat/server/ChatProvider.java +++ b/src/java/com/threerings/crowd/chat/server/ChatProvider.java @@ -38,6 +38,7 @@ import com.threerings.presents.server.InvocationManager; import com.threerings.presents.server.InvocationProvider; import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.CrowdCodes; import com.threerings.crowd.data.OccupantInfo; import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.server.CrowdServer; @@ -58,32 +59,29 @@ public class ChatProvider public static interface TellAutoResponder { /** - * Called following the delivery of message from - * teller to tellee. + * Called following the delivery of message from teller to + * tellee. */ - public void sentTell (BodyObject teller, BodyObject tellee, - String message); + public void sentTell (BodyObject teller, BodyObject tellee, String message); } /** Used to forward tells between servers in a multi-server setup. */ public static interface TellForwarder { /** - * Requests that the supplied tell message be delivered to the - * appropriate destination. + * Requests that the supplied tell message be delivered to the appropriate destination. * * @return true if the tell was delivered, false otherwise. */ - public boolean forwardTell (UserMessage message, Name target, - TellListener listener); + public boolean forwardTell (UserMessage message, Name target, TellListener listener); } /** - * Set an object to which all broadcasts should be sent, rather - * than iterating over the place objects and sending to each of them. + * Set an object to which all broadcasts should be sent, rather than iterating over the place + * objects and sending to each of them. * - * @param object an object to send all broadcasts, or null to send to - * each place object instead. + * @param object an object to send all broadcasts, or null to send to each place object + * instead. */ public void setAlternateBroadcastObject (DObject object) { @@ -91,11 +89,10 @@ public class ChatProvider } /** - * Set the auto tell responder for the chat provider. Only one auto - * responder is allowed. Note: this only works for same-server - * tells. If the tell is forwarded to another server, no auto-response - * opportunity is provided (because we never have both body objects in the - * same place). + * Set the auto tell responder for the chat provider. Only one auto responder is allowed. + * Note: this only works for same-server tells. If the tell is forwarded to another + * server, no auto-response opportunity is provided (because we never have both body objects in + * the same place). */ public void setTellAutoResponder (TellAutoResponder autoRespond) { @@ -103,9 +100,8 @@ public class ChatProvider } /** - * Configures the tell forwarded for the chat provider. This is used by the - * Crowd peer services to forward tells between servers in a multi-server - * cluster. + * Configures the tell forwarded for the chat provider. This is used by the Crowd peer services + * to forward tells between servers in a multi-server cluster. */ public void setTellForwarder (TellForwarder forwarder) { @@ -113,21 +109,18 @@ public class ChatProvider } /** - * Initializes the chat services and registers a chat provider with - * the invocation manager. + * Initializes the chat services and registers a chat provider with the invocation manager. */ public void init (InvocationManager invmgr, DObjectManager omgr) { // register a chat provider with the invocation manager - invmgr.registerDispatcher(new ChatDispatcher(this), true); + invmgr.registerDispatcher(new ChatDispatcher(this), CrowdCodes.CROWD_GROUP); } /** - * Processes a request from a client to deliver a tell message to - * another client. + * Processes a request from a client to deliver a tell message to another client. */ - public void tell (ClientObject caller, Name target, String message, - TellListener listener) + public void tell (ClientObject caller, Name target, String message, TellListener listener) throws InvocationException { // ensure that the caller has normal chat privileges @@ -142,8 +135,7 @@ public class ChatProvider // inform the auto-responder if needed BodyObject targobj; - if (_autoRespond != null && - (targobj = CrowdServer.lookupBody(target)) != null) { + if (_autoRespond != null && (targobj = CrowdServer.lookupBody(target)) != null) { _autoRespond.sentTell(source, targobj, message); } } @@ -151,8 +143,7 @@ public class ChatProvider /** * Processes a {@link ChatService#broadcast} request. */ - public void broadcast (ClientObject caller, String message, - InvocationListener listener) + public void broadcast (ClientObject caller, String message, InvocationListener listener) throws InvocationException { // make sure the requesting user has broadcast privileges @@ -170,20 +161,19 @@ public class ChatProvider public void away (ClientObject caller, String message) { BodyObject body = (BodyObject)caller; - // we modify this field via an invocation service request because - // a body object is not modifiable by the client + // we modify this field via an invocation service request because a body object is not + // modifiable by the client body.setAwayMessage(message); } /** * Broadcast the specified message to all places in the game. * - * @param from the user the broadcast is from, or null to send the message - * as a system message. + * @param from the user the broadcast is from, or null to send the message as a system message. * @param bundle the bundle, or null if the message needs no translation. * @param msg the content of the message to broadcast. - * @param attention if true, the message is sent as ATTENTION level, - * otherwise as INFO. Ignored if from is non-null. + * @param attention if true, the message is sent as ATTENTION level, otherwise as INFO. Ignored + * if from is non-null. */ public void broadcast (Name from, String bundle, String msg, boolean attention) @@ -203,12 +193,10 @@ public class ChatProvider } /** - * Delivers a tell message to the specified target and notifies the - * supplied listener of the result. It is assumed that the teller has - * already been permissions checked. + * Delivers a tell message to the specified target and notifies the supplied listener of the + * result. It is assumed that the teller has already been permissions checked. */ - public void deliverTell (UserMessage message, Name target, - TellListener listener) + public void deliverTell (UserMessage message, Name target, TellListener listener) throws InvocationException { // make sure the target user is online @@ -225,8 +213,7 @@ public class ChatProvider if (tobj.status == OccupantInfo.DISCONNECTED) { String errmsg = MessageBundle.compose( USER_DISCONNECTED, TimeUtil.getTimeOrderString( - System.currentTimeMillis() - tobj.statusTime, - TimeUtil.SECOND)); + System.currentTimeMillis() - tobj.statusTime, TimeUtil.SECOND)); throw new InvocationException(errmsg); } @@ -246,9 +233,9 @@ public class ChatProvider } /** - * Delivers a tell notification to the specified target player. It is - * assumed that the message is coming from some server entity and need not - * be permissions checked or notified of the result. + * Delivers a tell notification to the specified target player. It is assumed that the message + * is coming from some server entity and need not be permissions checked or notified of the + * result. */ public void deliverTell (BodyObject target, UserMessage message) { @@ -269,8 +256,8 @@ public class ChatProvider /** * Direct a broadcast to the specified object. */ - protected void broadcastTo (DObject object, Name from, String bundle, - String msg, boolean attention) + protected void broadcastTo (DObject object, Name from, String bundle, String msg, + boolean attention) { if (from == null) { if (attention) { @@ -280,8 +267,7 @@ public class ChatProvider } } else { - SpeakProvider.sendSpeak(object, from, bundle, msg, - BROADCAST_MODE); + SpeakProvider.sendSpeak(object, from, bundle, msg, BROADCAST_MODE); } } diff --git a/src/java/com/threerings/crowd/client/LocationDirector.java b/src/java/com/threerings/crowd/client/LocationDirector.java index dd45e034e..4bc07ad6d 100644 --- a/src/java/com/threerings/crowd/client/LocationDirector.java +++ b/src/java/com/threerings/crowd/client/LocationDirector.java @@ -34,6 +34,7 @@ import com.threerings.presents.dobj.Subscriber; import com.threerings.crowd.Log; import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.CrowdCodes; import com.threerings.crowd.data.LocationCodes; import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.data.PlaceObject; @@ -416,12 +417,17 @@ public class LocationDirector extends BasicDirector _lservice = null; } + @Override // from BasicDirector + protected void registerServices (Client client) + { + client.addServiceGroup(CrowdCodes.CROWD_GROUP); + } + // documentation inherited protected void fetchServices (Client client) { // obtain our service handle - _lservice = (LocationService) - client.requireService(LocationService.class); + _lservice = (LocationService)client.requireService(LocationService.class); } protected void gotBodyObject (BodyObject clobj) diff --git a/src/java/com/threerings/crowd/data/CrowdCodes.java b/src/java/com/threerings/crowd/data/CrowdCodes.java new file mode 100644 index 000000000..badc8b523 --- /dev/null +++ b/src/java/com/threerings/crowd/data/CrowdCodes.java @@ -0,0 +1,33 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.crowd.data; + +import com.threerings.presents.data.InvocationCodes; + +/** + * Codes and constants global to the Crowd services. + */ +public interface CrowdCodes extends InvocationCodes +{ + /** Defines our invocation services group. */ + public static final String CROWD_GROUP = "crowd"; +} diff --git a/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java b/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java index a30293463..c5b5cf5eb 100644 --- a/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java +++ b/src/java/com/threerings/crowd/peer/server/CrowdPeerManager.java @@ -99,8 +99,7 @@ public class CrowdPeerManager extends PeerManager // unregister our invocation service if (_nodeobj != null) { - CrowdServer.invmgr.clearDispatcher( - ((CrowdNodeObject)_nodeobj).crowdPeerService); + CrowdServer.invmgr.clearDispatcher(((CrowdNodeObject)_nodeobj).crowdPeerService); } // clear our tell forwarder registration @@ -136,7 +135,7 @@ public class CrowdPeerManager extends PeerManager CrowdNodeObject cnobj = (CrowdNodeObject)_nodeobj; cnobj.setCrowdPeerService( (CrowdPeerMarshaller)CrowdServer.invmgr.registerDispatcher( - new CrowdPeerDispatcher(this), false)); + new CrowdPeerDispatcher(this))); // register ourselves as a tell forwarder CrowdServer.chatprov.setTellForwarder(this); diff --git a/src/java/com/threerings/crowd/server/BodyProvider.java b/src/java/com/threerings/crowd/server/BodyProvider.java index 31b5933f3..3e313b837 100644 --- a/src/java/com/threerings/crowd/server/BodyProvider.java +++ b/src/java/com/threerings/crowd/server/BodyProvider.java @@ -28,6 +28,7 @@ import com.threerings.presents.server.InvocationProvider; import com.threerings.crowd.Log; import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.CrowdCodes; import com.threerings.crowd.data.OccupantInfo; /** @@ -54,7 +55,7 @@ public class BodyProvider public static void init (InvocationManager invmgr) { // register a provider instance - invmgr.registerDispatcher(new BodyDispatcher(new BodyProvider()), true); + invmgr.registerDispatcher(new BodyDispatcher(new BodyProvider()), CrowdCodes.CROWD_GROUP); } /** diff --git a/src/java/com/threerings/crowd/server/PlaceManager.java b/src/java/com/threerings/crowd/server/PlaceManager.java index 2c4be3b8f..b0bf28ca0 100644 --- a/src/java/com/threerings/crowd/server/PlaceManager.java +++ b/src/java/com/threerings/crowd/server/PlaceManager.java @@ -59,35 +59,28 @@ import com.threerings.crowd.chat.server.SpeakDispatcher; import com.threerings.crowd.chat.server.SpeakProvider; /** - * The place manager is the server-side entity that handles all - * place-related interaction. It subscribes to the place object and reacts - * to message and other events. Behavior specific to a place (or class of - * places) should live in the place manager. An intelligently constructed - * hierarchy of place manager classes working in concert with invocation - * services should provide the majority of the server-side functionality - * of an application built on the Presents platform. + * The place manager is the server-side entity that handles all place-related interaction. It + * subscribes to the place object and reacts to message and other events. Behavior specific to a + * place (or class of places) should live in the place manager. An intelligently constructed + * hierarchy of place manager classes working in concert with invocation services should provide + * the majority of the server-side functionality of an application built on the Presents platform. * - *

The base place manager class takes care of the necessary - * interactions with the place registry to manage place registration. It - * handles the place-related component of chatting. It also provides the - * basis for place-based access control. + *

The base place manager class takes care of the necessary interactions with the place + * registry to manage place registration. It handles the place-related component of chatting. It + * also provides the basis for place-based access control. * - *

A derived class is expected to handle initialization, cleanup and - * operational functionality via the calldown functions {@link #didInit}, - * {@link #didStartup}, and {@link #didShutdown} as well as through event - * listeners. + *

A derived class is expected to handle initialization, cleanup and operational functionality + * via the calldown functions {@link #didInit}, {@link #didStartup}, and {@link #didShutdown} as + * well as through event listeners. */ public class PlaceManager - implements MessageListener, OidListListener, ObjectDeathListener, - SpeakProvider.SpeakerValidator + implements MessageListener, OidListListener, ObjectDeathListener, SpeakProvider.SpeakerValidator { /** - * An interface used to allow the registration of standard message handlers - * to be invoked by the place manager when particular types of message - * events are received. + * An interface used to allow the registration of standard message handlers to be invoked by + * the place manager when particular types of message events are received. * - * @deprecated Use dynamically bound methods instead. See {@link - * DynamicListener}. + * @deprecated Use dynamically bound methods instead. See {@link DynamicListener}. */ @Deprecated public static interface MessageHandler @@ -96,8 +89,7 @@ public class PlaceManager * Invokes this message handler on the supplied event. * * @param event the message event received. - * @param pmgr the place manager for which the message is being - * handled. + * @param pmgr the place manager for which the message is being handled. */ public void handleEvent (MessageEvent event, PlaceManager pmgr); } @@ -119,9 +111,8 @@ public class PlaceManager } /** - * Returns the occupant info record for the user with the specified - * body oid, if they are an occupant of this room. Returns null - * otherwise. + * Returns the occupant info record for the user with the specified body oid, if they are an + * occupant of this room. Returns null otherwise. */ public OccupantInfo getOccupantInfo (int bodyOid) { @@ -129,8 +120,7 @@ public class PlaceManager } /** - * Applies the supplied occupant operation to each occupant currently - * present in this place. + * Applies the supplied occupant operation to each occupant currently present in this place. */ public void applyToOccupants (OccupantOp op) { @@ -143,11 +133,10 @@ public class PlaceManager } /** - * Updates the occupant info for this room occupant. Note: - * This must be used rather than setting the occupant info directly to - * avoid possible complications due to rapid fire changes to a user's - * occupant info. The occupant info record supplied to this method - * must be one returned from {@link #getOccupantInfo}. For example: + * Updates the occupant info for this room occupant. Note: This must be used rather + * than setting the occupant info directly to avoid possible complications due to rapid fire + * changes to a user's occupant info. The occupant info record supplied to this method must be + * one returned from {@link #getOccupantInfo}. For example: * *

      * OccupantInfo info = _plmgr.getOccupantInfo(bodyOid);
@@ -159,17 +148,16 @@ public class PlaceManager
     {
         // update the canonical copy
         _occInfo.put(occInfo.getBodyOid(), occInfo);
-        // clone the canonical copy and send out an event updating the
-        // distributed set with that clone
+        // clone the canonical copy and send out an event updating the distributed set with that
+        // clone
         _plobj.updateOccupantInfo((OccupantInfo)occInfo.clone());
     }
 
     /**
      * Called by the place registry after creating this place manager.
      */
-    public void init (
-        PlaceRegistry registry, InvocationManager invmgr,
-        DObjectManager omgr, PlaceConfig config)
+    public void init (PlaceRegistry registry, InvocationManager invmgr,
+                      DObjectManager omgr, PlaceConfig config)
     {
         _registry = registry;
         _invmgr = invmgr;
@@ -181,11 +169,10 @@ public class PlaceManager
     }
 
     /**
-     * Called after this place manager has been initialized with its
-     * configuration information but before it has been started up with
-     * its place object reference. Derived classes can override this
-     * function and perform any basic initialization that they desire.
-     * They should of course be sure to call super.didInit().
+     * Called after this place manager has been initialized with its configuration information but
+     * before it has been started up with its place object reference. Derived classes can override
+     * this function and perform any basic initialization that they desire.  They should of course
+     * be sure to call super.didInit().
      */
     protected void didInit ()
     {
@@ -209,14 +196,12 @@ public class PlaceManager
     }
 
     /**
-     * Provides an opportunity for place managers to ratify the creation
-     * of a place based on whatever criterion they may require (based on
-     * information available to the manager at this post-init() but
-     * pre-startup() phase of initialization).
+     * Provides an opportunity for place managers to ratify the creation of a place based on
+     * whatever criterion they may require (based on information available to the manager at this
+     * post-init() but pre-startup() phase of initialization).
      *
-     * @return If a permissions check is to fail, the manager should
-     * return a translatable string explaining the failure.
-     * null should be returned if initialization is to be
+     * @return If a permissions check is to fail, the manager should return a translatable string
+     * explaining the failure.  null should be returned if initialization is to be
      * allowed to proceed.
      */
     public String checkPermissions ()
@@ -225,19 +210,18 @@ public class PlaceManager
     }
 
     /**
-     * Called by the place manager after the place object has been
-     * successfully created.
+     * Called by the place manager after the place object has been successfully created.
      */
     public void startup (PlaceObject plobj)
     {
         // keep track of this
         _plobj = plobj;
 
-        // we usually want to create and register a speaker service instance
-        // that clients can use to speak in this place
+        // we usually want to create and register a speaker service instance that clients can use
+        // to speak in this place
         if (shouldCreateSpeakService()) {
-            plobj.setSpeakService((SpeakMarshaller) _invmgr.registerDispatcher(
-                new SpeakDispatcher(new SpeakProvider(plobj, this)), false));
+            plobj.setSpeakService((SpeakMarshaller)_invmgr.registerDispatcher(
+                                      new SpeakDispatcher(new SpeakProvider(plobj, this))));
         }
 
         // we'll need to hear about place object events
@@ -255,8 +239,8 @@ public class PlaceManager
     }
 
     /**
-     * Causes the place object being managed by this place manager to be
-     * destroyed and the place manager to shut down.
+     * Causes the place object being managed by this place manager to be destroyed and the place
+     * manager to shut down.
      */
     public void shutdown ()
     {
@@ -273,12 +257,10 @@ public class PlaceManager
     }
 
     /**
-     * Provides an opportunity for the place manager to prevent bodies from
-     * entering.
+     * Provides an opportunity for the place manager to prevent bodies from entering.
      *
-     * @return null if the body can enter, otherwise a
-     * translatable message explaining the reason the body is blocked
-     * from entering
+     * @return null if the body can enter, otherwise a translatable message explaining
+     * the reason the body is blocked from entering
      */
     public String ratifyBodyEntry (BodyObject body)
     {
@@ -286,12 +268,11 @@ public class PlaceManager
     }
 
     /**
-     * Builds an {@link OccupantInfo} record for the specified body object and
-     * inserts it into our place object. This is called by the location
-     * services when a body enters a place. If a derived class wishes to
-     * perform custom actions when an occupant is being inserted into a room,
-     * they should override {@link #insertOccupantInfo}, if they want to react
-     * to a body having entered, they should override {@link #bodyEntered}.
+     * Builds an {@link OccupantInfo} record for the specified body object and inserts it into our
+     * place object. This is called by the location services when a body enters a place. If a
+     * derived class wishes to perform custom actions when an occupant is being inserted into a
+     * room, they should override {@link #insertOccupantInfo}, if they want to react to a body
+     * having entered, they should override {@link #bodyEntered}.
      */
     public OccupantInfo buildOccupantInfo (BodyObject body)
     {
@@ -299,8 +280,8 @@ public class PlaceManager
             // create a new occupant info instance
             OccupantInfo info = body.createOccupantInfo(_plobj);
 
-            // insert the occupant info into our canonical table; this is done
-            // in a method so that derived classes
+            // insert the occupant info into our canonical table; this is done in a method so that
+            // derived classes
             insertOccupantInfo(info, body);
 
             // clone the canonical copy and insert it into the DSet
@@ -309,23 +290,21 @@ public class PlaceManager
             return info;
 
         } catch (Exception e) {
-            Log.warning("Failure building occupant info " +
-                        "[where=" + where() + ", body=" + body + "].");
+            Log.warning("Failure building occupant info [where=" + where() +
+                        ", body=" + body + "].");
             Log.logStackTrace(e);
             return null;
         }
     }
 
     /**
-     * Registers a particular message handler instance to be used when
-     * processing message events with the specified name.
+     * Registers a particular message handler instance to be used when processing message events
+     * with the specified name.
      *
-     * @param name the message name of the message events that should be
-     * handled by this handler.
+     * @param name the message name of the message events that should be handled by this handler.
      * @param handler the handler to be registered.
      *
-     * @deprecated Use dynamically bound methods instead. See {@link
-     * DynamicListener}.
+     * @deprecated Use dynamically bound methods instead. See {@link DynamicListener}.
      */
     @Deprecated
     public void registerMessageHandler (String name, MessageHandler handler)
@@ -348,11 +327,10 @@ public class PlaceManager
             handler.handleEvent(event, this);
         }
 
-        // the first argument should be the client object of the caller or null
-        // if it is a server originated event
+        // the first argument should be the client object of the caller or null if it is a server
+        // originated event
         int srcoid = event.getSourceOid();
-        DObject source = (srcoid <= 0) ?
-            null : CrowdServer.omgr.getObject(srcoid);
+        DObject source = (srcoid <= 0) ? null : CrowdServer.omgr.getObject(srcoid);
         Object[] args = event.getArgs(), nargs;
         if (args == null) {
             nargs = new Object[] { source };
@@ -391,30 +369,26 @@ public class PlaceManager
     }
 
     // documentation inherited from interface
-    public boolean isValidSpeaker (
-        DObject speakObj, ClientObject speaker, byte mode)
+    public boolean isValidSpeaker (DObject speakObj, ClientObject speaker, byte mode)
     {
         // only allow people in the room to speak
         return _plobj.occupants.contains(speaker.getOid());
     }
 
     /**
-     * Returns a string that can be used in log messages to identify the
-     * place as sensibly as possible to the developer who has to puzzle
-     * over log output trying to figure out what's going on. Derived place
-     * managers can override this and augment the default value (which is
+     * Returns a string that can be used in log messages to identify the place as sensibly as
+     * possible to the developer who has to puzzle over log output trying to figure out what's
+     * going on. Derived place managers can override this and augment the default value (which is
      * simply the place object id) with useful identifying information.
      */
     public String where ()
     {
-        return (_plobj == null) ?
-            StringUtil.shortClassName(this) + ":-1" : _plobj.which();
+        return (_plobj == null) ? StringUtil.shortClassName(this) + ":-1" : _plobj.which();
     }
 
     /**
-     * Generates a string representation of this manager. Does so in a way
-     * that makes it easier for derived classes to add to the string
-     * representation.
+     * Generates a string representation of this manager. Does so in a way that makes it easier for
+     * derived classes to add to the string representation.
      *
      * @see #toString(StringBuilder)
      */
@@ -428,8 +402,8 @@ public class PlaceManager
     }
 
     /**
-     * Derived classes will generally override this method to create a custom
-     * {@link PlaceObject} derivation that contains extra information.
+     * Derived classes will generally override this method to create a custom {@link PlaceObject}
+     * derivation that contains extra information.
      */
     protected PlaceObject createPlaceObject ()
     {
@@ -450,17 +424,16 @@ public class PlaceManager
     }
 
     /**
-     * Called if the permissions check failed, to give place managers a
-     * chance to do any cleanup that might be necessary due to their early
-     * initialization or permissions checking code.
+     * Called if the permissions check failed, to give place managers a chance to do any cleanup
+     * that might be necessary due to their early initialization or permissions checking code.
      */
     protected void permissionsFailed ()
     {
     }
 
     /**
-     * @return true if we should create a speaker service for our place object
-     * so that clients can use it to speak in this place.
+     * @return true if we should create a speaker service for our place object so that clients can
+     * use it to speak in this place.
      */
     protected boolean shouldCreateSpeakService ()
     {
@@ -468,8 +441,8 @@ public class PlaceManager
     }
 
     /**
-     * Creates an access controller for this place's distributed object, which
-     * by default is {@link CrowdObjectAccess#PLACE}.
+     * Creates an access controller for this place's distributed object, which by default is {@link
+     * CrowdObjectAccess#PLACE}.
      */
     protected AccessController getAccessController ()
     {
@@ -477,10 +450,9 @@ public class PlaceManager
     }
 
     /**
-     * Derived classes should override this (and be sure to call
-     * super.didStartup()) to perform any startup time
-     * initialization. The place object will be available by the time this
-     * method is executed.
+     * Derived classes should override this (and be sure to call super.didStartup())
+     * to perform any startup time initialization. The place object will be available by the time
+     * this method is executed.
      */
     protected void didStartup ()
     {
@@ -493,10 +465,9 @@ public class PlaceManager
     }
 
     /**
-     * Called when this place has been destroyed and the place manager has
-     * shut down (via a call to {@link #shutdown}). Derived classes can
-     * override this method and perform any necessary shutdown time
-     * processing.
+     * Called when this place has been destroyed and the place manager has shut down (via a call to
+     * {@link #shutdown}). Derived classes can override this method and perform any necessary
+     * shutdown time processing.
      */
     protected void didShutdown ()
     {
@@ -509,10 +480,9 @@ public class PlaceManager
     }
 
     /**
-     * Called when an occupant is being added to this place. This will be
-     * before the call to {@link #bodyEntered} and gives the derived class a
-     * chance to set up additional information about the occupant that might
-     * not be tracked in the occupant info.
+     * Called when an occupant is being added to this place. This will be before the call to {@link
+     * #bodyEntered} and gives the derived class a chance to set up additional information about
+     * the occupant that might not be tracked in the occupant info.
      */
     protected void insertOccupantInfo (OccupantInfo info, BodyObject body)
     {
@@ -525,8 +495,7 @@ public class PlaceManager
     protected void bodyEntered (final int bodyOid)
     {
         if (Log.log.getLevel() == Level.FINE) {
-            Log.debug("Body entered [where=" + where() +
-                      ", oid=" + bodyOid + "].");
+            Log.debug("Body entered [where=" + where() + ", oid=" + bodyOid + "].");
         }
 
         // let our delegates know what's up
@@ -546,13 +515,11 @@ public class PlaceManager
     protected void bodyLeft (final int bodyOid)
     {
         if (Log.log.getLevel() == Level.FINE) {
-            Log.debug("Body left [where=" + where() +
-                      ", oid=" + bodyOid + "].");
+            Log.debug("Body left [where=" + where() + ", oid=" + bodyOid + "].");
         }
 
-        // if their occupant info hasn't been removed (which may be the
-        // case if they logged off rather than left via a MoveTo request),
-        // we need to get it on out of here
+        // if their occupant info hasn't been removed (which may be the case if they logged off
+        // rather than left via a MoveTo request), we need to get it on out of here
         Integer key = Integer.valueOf(bodyOid);
         if (_plobj.occupantInfo.containsKey(key)) {
             _plobj.removeFromOccupantInfo(key);
@@ -575,8 +542,7 @@ public class PlaceManager
     }
 
     /**
-     * Returns whether the location should be marked as empty and potentially
-     *  shutdown.
+     * Returns whether the location should be marked as empty and potentially shutdown.
      */
     protected boolean shouldDeclareEmpty (OccupantInfo leaver)
     {
@@ -597,10 +563,9 @@ public class PlaceManager
     }
 
     /**
-     * Called when we transition from having bodies in the place to not
-     * having any bodies in the place. Some places may take this as a sign
-     * to pack it in, others may wish to stick around. In any case, they
-     * can override this method to do their thing.
+     * Called when we transition from having bodies in the place to not having any bodies in the
+     * place. Some places may take this as a sign to pack it in, others may wish to stick
+     * around. In any case, they can override this method to do their thing.
      */
     protected void placeBecameEmpty ()
     {
@@ -646,9 +611,9 @@ public class PlaceManager
     }
 
     /**
-     * Returns the period (in milliseconds) of emptiness after which this
-     * place manager will unload itself and shutdown. Returning
-     * 0 indicates that the place should never be shutdown.
+     * Returns the period (in milliseconds) of emptiness after which this place manager will unload
+     * itself and shutdown. Returning 0 indicates that the place should never be
+     * shutdown.
      */
     protected long idleUnloadPeriod ()
     {
@@ -656,9 +621,8 @@ public class PlaceManager
     }
 
     /**
-     * An extensible way to add to the string representation of this
-     * class. Override this (being sure to call super) and append your
-     * info to the buffer.
+     * An extensible way to add to the string representation of this class. Override this (being
+     * sure to call super) and append your info to the buffer.
      */
     protected void toString (StringBuilder buf)
     {
@@ -699,8 +663,7 @@ public class PlaceManager
     /** A reference to the place registry with which we're registered. */
     protected PlaceRegistry _registry;
 
-    /** The invocation manager with whom we register our game invocation
-     * services. */
+    /** The invocation manager with whom we register our game invocation services. */
     protected InvocationManager _invmgr;
 
     /** A distributed object manager for doing dobj stuff. */
@@ -719,12 +682,10 @@ public class PlaceManager
     protected ArrayList _delegates;
 
     /** Used to keep a canonical copy of the occupant info records. */
-    protected HashIntMap _occInfo =
-        new HashIntMap();
+    protected HashIntMap _occInfo = new HashIntMap();
 
-    /** The interval currently registered to shut this place
-     * down after a certain period of idility, or null if no
-     * interval is currently registered. */
+    /** The interval currently registered to shut this place down after a certain period of
+     * idility, or null if no interval is currently registered. */
     protected Interval _shutdownInterval;
 
     /** Used to do method lookup magic when we receive message events. */
diff --git a/src/java/com/threerings/crowd/server/PlaceRegistry.java b/src/java/com/threerings/crowd/server/PlaceRegistry.java
index b37ae598f..63eca4ff2 100644
--- a/src/java/com/threerings/crowd/server/PlaceRegistry.java
+++ b/src/java/com/threerings/crowd/server/PlaceRegistry.java
@@ -30,6 +30,7 @@ import com.threerings.presents.server.InvocationException;
 import com.threerings.presents.server.InvocationManager;
 
 import com.threerings.crowd.Log;
+import com.threerings.crowd.data.CrowdCodes;
 import com.threerings.crowd.data.PlaceConfig;
 import com.threerings.crowd.data.PlaceObject;
 
@@ -59,7 +60,7 @@ public class PlaceRegistry
     {
         // create and register our location provider
         locprov = new LocationProvider(invmgr, omgr, this);
-        invmgr.registerDispatcher(new LocationDispatcher(locprov), true);
+        invmgr.registerDispatcher(new LocationDispatcher(locprov), CrowdCodes.CROWD_GROUP);
 
         // we'll need these later
         _omgr = omgr;
diff --git a/src/java/com/threerings/presents/client/BasicDirector.java b/src/java/com/threerings/presents/client/BasicDirector.java
index 701650058..f22e16a76 100644
--- a/src/java/com/threerings/presents/client/BasicDirector.java
+++ b/src/java/com/threerings/presents/client/BasicDirector.java
@@ -24,19 +24,17 @@ 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}).
+ * 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.
+     * 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)
     {
@@ -56,6 +54,12 @@ public class BasicDirector
         }
     }
 
+    // documentation inherited from interface
+    public void clientWillLogon (Client client)
+    {
+        registerServices(client);
+    }
+
     // documentation inherited from interface
     public void clientDidLogon (Client client)
     {
@@ -85,8 +89,7 @@ public class BasicDirector
     }
     
     /**
-     * Checks whether or not this director is available in standalone mode
-     * (defaults to false).
+     * Checks whether or not this director is available in standalone mode (defaults to false).
      */
     public boolean isAvailableInStandalone ()
     {
@@ -102,8 +105,7 @@ public class BasicDirector
     }
     
     /**
-     * If this director is not currently available, throws a
-     * {@link RuntimeException}.
+     * If this director is not currently available, throws a {@link RuntimeException}.
      */
     protected void assertAvailable ()
     {
@@ -114,20 +116,27 @@ public class BasicDirector
     }
     
     /**
-     * Called in three circumstances: when a director is created and we've
-     * already logged on; when we first log on and when the client object
-     * changes after we've already logged on.
+     * Called in three circumstances: when a director is created and we've already logged on; when
+     * we first log on and when the client object changes after we've already logged on.
      */
     protected void clientObjectUpdated (Client client)
     {
     }
 
     /**
-     * Derived directors can override this method and obtain any services
-     * they'll need during their operation via calls to {@link
-     * Client#getService}. If the director is available, 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.
+     * If a director makes use of bootstrap invocation services which are part of a bootstrap
+     * service group, it should register interest in that group here with a call to {@link
+     * Client#addServiceGroup}.
+     */
+    protected void registerServices (Client client)
+    {
+    }
+
+    /**
+     * Derived directors can override this method and obtain any services they'll need during their
+     * operation via calls to {@link Client#getService}. If the director is available, 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)
     {
diff --git a/src/java/com/threerings/presents/client/Client.java b/src/java/com/threerings/presents/client/Client.java
index 004cfb4a1..5fd5f6af5 100644
--- a/src/java/com/threerings/presents/client/Client.java
+++ b/src/java/com/threerings/presents/client/Client.java
@@ -30,6 +30,7 @@ import com.samskivert.util.RunQueue;
 
 import com.threerings.presents.Log;
 import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.data.InvocationCodes;
 import com.threerings.presents.dobj.DObjectManager;
 import com.threerings.presents.net.AuthResponseData;
 import com.threerings.presents.net.BootstrapData;
@@ -44,8 +45,7 @@ import com.threerings.presents.net.PongResponse;
  */
 public class Client
 {
-    /** The default ports on which the server listens for client
-     * connections. */
+    /** The default ports on which the server listens for client connections. */
     public static final int[] DEFAULT_SERVER_PORTS = { 47624 };
 
     /**
@@ -193,16 +193,6 @@ public class Client
         }
     }
 
-    /**
-     * Marks this client as interested in the specified bootstrap services group. Any services
-     * registered as bootstrap services with the supplied group name will be included in this
-     * clients bootstrap services set. This must be called before {@link #logon}.
-     */
-    public void addBootstrapGroup (String group)
-    {
-        _bootGroups.add(group);
-    }
-
     /**
      * Returns the data associated with our authentication response. Users of the Presents system
      * may wish to communicate authentication related information to their client by extending and
@@ -269,6 +259,16 @@ public class Client
         return _invdir;
     }
 
+    /**
+     * Marks this client as interested in the specified bootstrap services group. Any services
+     * registered as bootstrap services with the supplied group name will be included in this
+     * clients bootstrap services set. This must be called before {@link #logon}.
+     */
+    public void addServiceGroup (String group)
+    {
+        _bootGroups.add(group);
+    }
+
     /**
      * Returns the first bootstrap service that could be located that implements the supplied
      * {@link InvocationService} derivation.  null is returned if no such service
@@ -281,8 +281,7 @@ public class Client
         }
         int scount = _bstrap.services.size();
         for (int ii = 0; ii < scount; ii++) {
-            InvocationService service = (InvocationService)
-                _bstrap.services.get(ii);
+            InvocationService service = (InvocationService)_bstrap.services.get(ii);
             if (sclass.isInstance(service)) {
                 return service;
             }
@@ -300,8 +299,7 @@ public class Client
         InvocationService isvc = getService(sclass);
         if (isvc == null) {
             throw new RuntimeException(
-                sclass.getName() + " isn't available. " +
-                "I can't bear to go on.");
+                sclass.getName() + " isn't available. I can't bear to go on.");
         }
         return isvc;
     }
@@ -744,7 +742,9 @@ public class Client
     protected BootstrapData _bstrap;
 
     /** The set of bootstrap service groups this client cares about. */
-    protected HashSet _bootGroups = new HashSet();
+    protected HashSet _bootGroups = new HashSet(); {
+        _bootGroups.add(InvocationCodes.GLOBAL_GROUP);
+    }
 
     /** Manages invocation services. */
     protected InvocationDirector _invdir = new InvocationDirector();
diff --git a/src/java/com/threerings/presents/client/ClientAdapter.java b/src/java/com/threerings/presents/client/ClientAdapter.java
index d0cf7759c..88f45186e 100644
--- a/src/java/com/threerings/presents/client/ClientAdapter.java
+++ b/src/java/com/threerings/presents/client/ClientAdapter.java
@@ -32,6 +32,11 @@ package com.threerings.presents.client;
  */
 public class ClientAdapter implements ClientObserver
 {
+    // documentation inherited
+    public void clientWillLogon (Client client)
+    {
+    }
+
     // documentation inherited
     public void clientDidLogon (Client client)
     {
diff --git a/src/java/com/threerings/presents/client/SessionObserver.java b/src/java/com/threerings/presents/client/SessionObserver.java
index 1f43f4d18..5f273cd81 100644
--- a/src/java/com/threerings/presents/client/SessionObserver.java
+++ b/src/java/com/threerings/presents/client/SessionObserver.java
@@ -30,6 +30,11 @@ package com.threerings.presents.client;
  */
 public interface SessionObserver
 {
+    /**
+     * Called immediately before a logon is attempted.
+     */
+    public void clientWillLogon (Client client);
+
     /**
      * Called after the client successfully connected to and authenticated
      * with the server. The entire object system is up and running by the
diff --git a/src/java/com/threerings/presents/data/InvocationCodes.java b/src/java/com/threerings/presents/data/InvocationCodes.java
index d8aaadd3c..8f5a1369c 100644
--- a/src/java/com/threerings/presents/data/InvocationCodes.java
+++ b/src/java/com/threerings/presents/data/InvocationCodes.java
@@ -29,6 +29,10 @@ package com.threerings.presents.data;
  */
 public interface InvocationCodes
 {
+    /** Defines a global invocation services group that can be used by clients and services that do
+     * not care to make a distinction between groups of invocation services. */
+    public static final String GLOBAL_GROUP = "presents";
+
     /** An error code returned to clients when a service cannot be performed because of some
      * internal server error that we couldn't explain in any meaningful way (things like null
      * pointer exceptions). */
diff --git a/src/java/com/threerings/presents/peer/server/PeerManager.java b/src/java/com/threerings/presents/peer/server/PeerManager.java
index e4b2247cb..3dbf42db0 100644
--- a/src/java/com/threerings/presents/peer/server/PeerManager.java
+++ b/src/java/com/threerings/presents/peer/server/PeerManager.java
@@ -160,8 +160,7 @@ public class PeerManager
 
         // set the invocation service
         _nodeobj.setPeerService(
-            (PeerMarshaller)PresentsServer.invmgr.registerDispatcher(
-                new PeerDispatcher(this), false));
+            (PeerMarshaller)PresentsServer.invmgr.registerDispatcher(new PeerDispatcher(this)));
 
         // register ourselves as a client observer
         PresentsServer.clmgr.addClientObserver(this);
@@ -668,6 +667,12 @@ public class PeerManager
             log.warning("Peer connection failed " + _record + ": " + cause);
         }
 
+        // documentation inherited from interface ClientObserver
+        public void clientWillLogon (Client client)
+        {
+            // nothing doing
+        }
+
         // documentation inherited from interface ClientObserver
         public void clientDidLogon (Client client)
         {
diff --git a/src/java/com/threerings/presents/server/InvocationManager.java b/src/java/com/threerings/presents/server/InvocationManager.java
index e5615245a..5efeccd2a 100644
--- a/src/java/com/threerings/presents/server/InvocationManager.java
+++ b/src/java/com/threerings/presents/server/InvocationManager.java
@@ -62,9 +62,6 @@ import java.util.HashMap;
 public class InvocationManager
     implements EventListener
 {
-    /** Defines the name of the global bootstrap group. */
-    public static final String GLOBAL_BOOTSTRAP_GROUP = "global";
-
     /** A mapping from bootstrap group to lists of services that are to be provided to clients at
      * boot time. Don't mess with these lists! */
     public HashMap> bootlists =
@@ -100,13 +97,10 @@ public class InvocationManager
      * send requests to the provider for whom the dispatcher is proxying.
      *
      * @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 InvocationMarshaller registerDispatcher (
-        InvocationDispatcher dispatcher, boolean bootstrap)
+    public InvocationMarshaller registerDispatcher (InvocationDispatcher dispatcher)
     {
-        return registerDispatcher(dispatcher, bootstrap ? GLOBAL_BOOTSTRAP_GROUP : null);
+        return registerDispatcher(dispatcher, null);
     }
 
     /**
@@ -140,8 +134,7 @@ public class InvocationManager
             list.add(marsh);
         }
 
-        _recentRegServices.put(Integer.valueOf(invCode),
-                               marsh.getClass().getName());
+        _recentRegServices.put(Integer.valueOf(invCode), marsh.getClass().getName());
 
 //        Log.info("Registered service [marsh=" + marsh + "].");
         return marsh;
@@ -292,8 +285,7 @@ public class InvocationManager
     /** 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. */
+    /** The object id of the object on which we receive invocation service requests. */
     protected int _invoid = -1;
 
     /** Used to generate monotonically increasing provider ids. */
@@ -303,7 +295,7 @@ public class InvocationManager
     protected HashIntMap _dispatchers =
         new HashIntMap();
 
-    /** The text that is appended to the procedure name when automatically
-     * generating a failure response. */
+    /** The text that is appended to the procedure name when automatically generating a failure
+     * response. */
     protected static final String FAILED_SUFFIX = "Failed";
 }
diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java
index 2e6af9228..bc9b09c07 100644
--- a/src/java/com/threerings/presents/server/PresentsClient.java
+++ b/src/java/com/threerings/presents/server/PresentsClient.java
@@ -651,13 +651,9 @@ public class PresentsClient
 
         // fill in the list of bootstrap services
         data.services = new StreamableArrayList();
-        StreamableArrayList list = 
-            PresentsServer.invmgr.bootlists.get(InvocationManager.GLOBAL_BOOTSTRAP_GROUP);
-        if (list != null) {
-            data.services.addAll(list);
-        }
         for (String group : _areq.getBootGroups()) {
-            list = PresentsServer.invmgr.bootlists.get(group);
+            StreamableArrayList list =
+                PresentsServer.invmgr.bootlists.get(group);
             if (list != null) {
                 data.services.addAll(list);
             }
diff --git a/src/java/com/threerings/presents/server/TimeBaseProvider.java b/src/java/com/threerings/presents/server/TimeBaseProvider.java
index c580cef77..20d91a532 100644
--- a/src/java/com/threerings/presents/server/TimeBaseProvider.java
+++ b/src/java/com/threerings/presents/server/TimeBaseProvider.java
@@ -34,17 +34,16 @@ import com.threerings.presents.data.TimeBaseObject;
 import com.threerings.presents.dobj.RootDObjectManager;
 
 /**
- * Provides the server-side of the time base services. The time base
- * services provide a means by which delta times can be sent over the
- * network which are expanded based on a shared base time into full time
- * stamps.
+ * Provides the server-side of the time base services. The time base services provide a means by
+ * which delta times can be sent over the network which are expanded based on a shared base time
+ * into full time stamps.
  */
 public class TimeBaseProvider
     implements InvocationProvider, TimeBaseCodes
 {
     /**
-     * Registers the time provider with the appropriate managers. Called
-     * by the presents server at startup.
+     * Registers the time provider with the appropriate managers. Called by the presents server at
+     * startup.
      */
     public static void init (InvocationManager invmgr, RootDObjectManager omgr)
     {
@@ -53,13 +52,12 @@ public class TimeBaseProvider
         _omgr = omgr;
 
         // register a provider instance
-        invmgr.registerDispatcher(
-            new TimeBaseDispatcher(new TimeBaseProvider()), true);
+        invmgr.registerDispatcher(new TimeBaseDispatcher(new TimeBaseProvider()), GLOBAL_GROUP);
     }
 
     /**
-     * Creates a time base object which can subsequently be fetched by the
-     * client and used to send delta times.
+     * Creates a time base object which can subsequently be fetched by the client and used to send
+     * delta times.
      *
      * @param timeBase the name of the time base to create.
      *
@@ -73,8 +71,8 @@ public class TimeBaseProvider
     }
 
     /**
-     * Returns the named timebase object, or null if no time base object
-     * has been created with that name.
+     * Returns the named timebase object, or null if no time base object has been created with that
+     * name.
      */
     public static TimeBaseObject getTimeBase (String timeBase)
     {
@@ -82,11 +80,9 @@ public class TimeBaseProvider
     }
 
     /**
-     * Processes a request from a client to fetch the oid of the specified
-     * time object.
+     * Processes a request from a client to fetch the oid of the specified time object.
      */
-    public void getTimeOid (
-        ClientObject source, String timeBase, GotTimeBaseListener listener)
+    public void getTimeOid (ClientObject source, String timeBase, GotTimeBaseListener listener)
         throws InvocationException
     {
         // look up the time base object in question
diff --git a/tests/src/java/com/threerings/crowd/client/JabberClient.java b/tests/src/java/com/threerings/crowd/client/JabberClient.java
index f2b4c5244..0f97eb7f8 100644
--- a/tests/src/java/com/threerings/crowd/client/JabberClient.java
+++ b/tests/src/java/com/threerings/crowd/client/JabberClient.java
@@ -100,6 +100,11 @@ public class JabberClient
         return _ctx;
     }
 
+    // documentation inherited from interface SessionObserver
+    public void clientWillLogon (Client client)
+    {
+    }
+
     // documentation inherited from interface SessionObserver
     public void clientDidLogon (Client client)
     {
diff --git a/tests/src/java/com/threerings/crowd/client/TestClient.java b/tests/src/java/com/threerings/crowd/client/TestClient.java
index 9bdba5b04..fab6a41c8 100644
--- a/tests/src/java/com/threerings/crowd/client/TestClient.java
+++ b/tests/src/java/com/threerings/crowd/client/TestClient.java
@@ -83,6 +83,10 @@ public class TestClient
         return _main == Thread.currentThread();
     }
 
+    public void clientWillLogon (Client client)
+    {
+    }
+
     public void clientDidLogon (Client client)
     {
         Log.info("Client did logon [client=" + client + "].");
diff --git a/tests/src/java/com/threerings/presents/client/TestClient.java b/tests/src/java/com/threerings/presents/client/TestClient.java
index 36963cf41..02dadb505 100644
--- a/tests/src/java/com/threerings/presents/client/TestClient.java
+++ b/tests/src/java/com/threerings/presents/client/TestClient.java
@@ -67,6 +67,11 @@ public class TestClient
         }
     }
 
+    public void clientWillLogon (Client client)
+    {
+        client.addServiceGroup("test");
+    }
+
     public void clientDidLogon (Client client)
     {
         Log.info("Client did logon [client=" + client + "].");
diff --git a/tests/src/java/com/threerings/presents/server/TestServer.java b/tests/src/java/com/threerings/presents/server/TestServer.java
index 968976991..6a9a45d14 100644
--- a/tests/src/java/com/threerings/presents/server/TestServer.java
+++ b/tests/src/java/com/threerings/presents/server/TestServer.java
@@ -35,7 +35,7 @@ public class TestServer extends PresentsServer
         super.init();
 
         // register our test provider
-        invmgr.registerDispatcher(new TestDispatcher(new TestManager()), true);
+        invmgr.registerDispatcher(new TestDispatcher(new TestManager()), "test");
 
         // create a test object
         testobj = omgr.registerObject(new TestObject());