// // $Id: PlaceManager.java,v 1.38 2002/10/06 03:31:16 mdb Exp $ package com.threerings.crowd.server; import java.util.ArrayList; import java.util.HashMap; import java.util.Properties; import com.samskivert.util.HashIntMap; import com.threerings.presents.data.ClientObject; import com.threerings.presents.server.InvocationManager; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObjectManager; import com.threerings.presents.dobj.MessageEvent; import com.threerings.presents.dobj.MessageListener; import com.threerings.presents.dobj.ObjectAddedEvent; import com.threerings.presents.dobj.ObjectDeathListener; import com.threerings.presents.dobj.ObjectDestroyedEvent; import com.threerings.presents.dobj.ObjectRemovedEvent; import com.threerings.presents.dobj.OidListListener; import com.threerings.crowd.Log; import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.OccupantInfo; import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.data.PlaceObject; import com.threerings.crowd.chat.SpeakDispatcher; import com.threerings.crowd.chat.SpeakMarshaller; import com.threerings.crowd.chat.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 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. */ public class PlaceManager 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. */ public static interface MessageHandler { /** * 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. */ public void handleEvent (MessageEvent event, PlaceManager pmgr); } /** * Returns a reference to our place configuration object. */ public PlaceConfig getConfig () { return _config; } /** * Returns the place object managed by this place manager. */ public PlaceObject getPlaceObject () { return _plobj; } /** * 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) { return (OccupantInfo)_occInfo.get(bodyOid); } /** * 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);
* // ... modifications made to 'info'
* _plmgr.updateOccupantInfo(info);
*
*/
public void updateOccupantInfo (OccupantInfo occInfo)
{
// 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
_plobj.updateOccupantInfo((OccupantInfo)occInfo.clone());
}
/**
* A place manager derived class is likely to have a corresponding
* derived class of {@link com.threerings.crowd.data.PlaceObject} that
* it will be managing. Derived classes should override this method
* and return the class object for the place object derived class they
* desire to use. The place registry will use this method to create
* the proper place object during the place creation process.
*
* @return the class of the class, derived from {@link PlaceObject},
* that this manager wishes to manage.
*
* @see PlaceRegistry#createPlace
*/
protected Class getPlaceObjectClass ()
{
return PlaceObject.class;
}
/**
* Called by the place registry after creating this place manager.
*/
public void init (
PlaceRegistry registry, InvocationManager invmgr,
DObjectManager omgr, PlaceConfig config)
{
_registry = registry;
_invmgr = invmgr;
_omgr = omgr;
_config = config;
// let derived classes do initialization stuff
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 ()
{
// initialize our delegates
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
delegate.didInit(_config);
}
});
}
/**
* 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
* allowed to proceed.
*/
public String checkPermissions ()
{
return null;
}
/**
* 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 ()
{
}
/**
* Called by the place manager after the place object has been
* successfully created.
*/
public void startup (PlaceObject plobj)
{
// keep track of this
_plobj = plobj;
// create and register a speaker service instance that clients can
// use to speak in this place
SpeakMarshaller speakService =
(SpeakMarshaller)_invmgr.registerDispatcher(
new SpeakDispatcher(new SpeakProvider(_plobj, this)), false);
plobj.setSpeakService(speakService);
// we'll need to hear about place object events
plobj.addListener(this);
// let our derived classes do their thang
didStartup();
}
/**
* 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 ()
{
// let our delegates know that we've started up
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
delegate.didStartup(_plobj);
}
});
}
/**
* Causes the place object being managed by this place manager to be
* destroyed and the place manager to shut down.
*/
public void shutdown ()
{
// destroy the object and everything will follow from that
CrowdServer.omgr.destroyObject(_plobj.getOid());
// clear out our services
_invmgr.clearDispatcher(_plobj.speakService);
}
/**
* 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 ()
{
// let our delegates know that we've shut down
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
delegate.didShutdown();
}
});
}
/**
* Returns the appropriate derived class of {@link OccupantInfo} that
* will be used to provide occupant info for this body. An occupant
* info record is created when a body enters a place.
*
* @param body the body that is entering the place and for whom we are
* creating an occupant info record.
*/
protected Class getOccupantInfoClass (BodyObject body)
{
return OccupantInfo.class;
}
/**
* Builds an occupant info 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. It should not be overridden by
* derived classes, they should override {@link
* #populateOccupantInfo}, which is set up for that sort of thing.
*/
public OccupantInfo buildOccupantInfo (BodyObject body)
{
try {
// create a new occupant info instance
OccupantInfo info = (OccupantInfo)
getOccupantInfoClass(body).newInstance();
// configure it with the appropriate values
populateOccupantInfo(info, body);
// insert the occupant info into our canonical table
_occInfo.put(info.getBodyOid(), info);
// clone the canonical copy and insert it into the DSet
_plobj.addToOccupantInfo((OccupantInfo)info.clone());
return info;
} catch (Exception e) {
Log.warning("Failure building occupant info " +
"[where=" + where() + ", body=" + body + "].");
Log.logStackTrace(e);
return null;
}
}
/**
* 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.
*/
protected String where ()
{
return String.valueOf(_plobj.getOid());
}
/**
* Derived classes should override this method if they are making use
* of a derived occupant info class. They should call the super
* implementation and then populate the occupant info fields in their
* extended object.
*/
protected void populateOccupantInfo (OccupantInfo info, BodyObject body)
{
// the base occupant info is only their username
info.bodyOid = new Integer(body.getOid());
info.username = body.username;
}
/**
* Called when a body object enters this place.
*/
protected void bodyEntered (final int bodyOid)
{
Log.debug("Body entered [where=" + where() + ", oid=" + bodyOid + "].");
// let our delegates know what's up
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
delegate.bodyEntered(bodyOid);
}
});
}
/**
* Called when a body object leaves this place.
*/
protected void bodyLeft (final int 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
Object key = new Integer(bodyOid);
if (_plobj.occupantInfo.containsKey(key)) {
_plobj.removeFromOccupantInfo(key);
}
// clear out their canonical (local) occupant info record
_occInfo.remove(bodyOid);
// let our delegates know what's up
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
delegate.bodyLeft(bodyOid);
}
});
// if that leaves us with zero occupants, maybe do something
if (_plobj.occupants.size() == 0) {
placeBecameEmpty();
}
}
/**
* 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 ()
{
// let our delegates know what's up
applyToDelegates(new DelegateOp() {
public void apply (PlaceManagerDelegate delegate) {
delegate.placeBecameEmpty();
}
});
}
/**
* 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 handler the handler to be registered.
*/
public void registerMessageHandler (String name, MessageHandler handler)
{
// create our handler map if necessary
if (_msghandlers == null) {
_msghandlers = new HashMap();
}
_msghandlers.put(name, handler);
}
/**
* Dispatches message events to registered message handlers. Derived
* classes should probably register message handlers rather than
* override this method directly.
*/
public void messageReceived (MessageEvent event)
{
MessageHandler handler = null;
if (_msghandlers != null) {
handler = (MessageHandler)_msghandlers.get(event.getName());
}
if (handler != null) {
handler.handleEvent(event, this);
}
}
/**
* Handles occupant arrival into the place. Derived classes may need
* to override this method to handle other oid lists in their derived
* place objects. They should be sure to call
* super.objectAdded if the event is one they don't
* explicitly handle.
*/
public void objectAdded (ObjectAddedEvent event)
{
if (event.getName().equals(PlaceObject.OCCUPANTS)) {
bodyEntered(event.getOid());
}
}
/**
* Handles occupant departure from the place. Derived classes may need
* to override this method to handle other oid lists in their derived
* place objects. They should be sure to call
* super.objectRemoved if the event is one they don't
* explicitly handle.
*/
public void objectRemoved (ObjectRemovedEvent event)
{
if (event.getName().equals(PlaceObject.OCCUPANTS)) {
bodyLeft(event.getOid());
}
}
/**
* Handles place destruction. We shut ourselves down and ask the place
* registry to unmap us.
*/
public void objectDestroyed (ObjectDestroyedEvent event)
{
// unregister ourselves
_registry.unmapPlaceManager(this);
// let our derived classes and delegates shut themselves down
didShutdown();
}
// documentation inherited from interface
public boolean isValidSpeaker (DObject speakObj, ClientObject speaker)
{
// only allow people in the room to speak
return _plobj.occupants.contains(speaker.getOid());
}
/**
* 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(StringBuffer)
*/
public String toString ()
{
StringBuffer buf = new StringBuffer();
buf.append("[");
toString(buf);
buf.append("]");
return buf.toString();
}
/**
* 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 (StringBuffer buf)
{
buf.append("place=").append(_plobj);
buf.append(", config=").append(_config);
}
/**
* Adds the supplied delegate to the list for this manager.
*/
protected void addDelegate (PlaceManagerDelegate delegate)
{
if (_delegates == null) {
_delegates = new ArrayList();
}
_delegates.add(delegate);
}
/**
* Used to call methods in delegates.
*/
protected static interface DelegateOp
{
public void apply (PlaceManagerDelegate delegate);
}
/**
* Applies the supplied operation to the registered delegates.
*/
protected void applyToDelegates (DelegateOp op)
{
if (_delegates != null) {
int dcount = _delegates.size();
for (int i = 0; i < dcount; i++) {
op.apply((PlaceManagerDelegate)_delegates.get(i));
}
}
}
/** 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. */
protected InvocationManager _invmgr;
/** A distributed object manager for doing dobj stuff. */
protected DObjectManager _omgr;
/** A reference to the place object that we manage. */
protected PlaceObject _plobj;
/** A reference to the configuration for our place. */
protected PlaceConfig _config;
/** Message handlers are used to process message events. */
protected HashMap _msghandlers;
/** A list of the delegates in use by this manager. */
protected ArrayList _delegates;
/** Used to keep a canonical copy of the occupant info records. */
protected HashIntMap _occInfo = new HashIntMap();
}