Moved location services implementation into LocationManager so that

LocationProvider can be an interface like its brothers and sisters.

Modified the place registry to create PlaceManager instances using the injector
so that we can inject dependencies into managers. Also modified the custom
classloader functionality so that it can/will circumvent this process
entirely. It's not possible to inject using a custom classloader and one
certainly would not want to do so.

Unfortunately this means we can't inject some basic managers into PlaceManager
because it needs to work for games loaded into a sandboxed classloader, but the
big win is that in everything other than GameManager and PlaceManager, we can
inject to our heart's content.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5168 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2008-06-08 17:18:26 +00:00
parent cfc240fa9c
commit bf77cb1520
8 changed files with 296 additions and 258 deletions
-1
View File
@@ -71,7 +71,6 @@
<exclude name="**/admin/**"/>
</fileset>
<providerless service="ChatService"/>
<providerless service="LocationService"/>
<providerless service="SimulatorService"/>
<providerless service="TimeBaseService"/>
</service>
@@ -84,6 +84,6 @@ public class CrowdClient extends PresentsClient
*/
protected void clearLocation (BodyObject bobj)
{
CrowdServer.plreg.locprov.leaveOccupiedPlace(bobj);
CrowdServer.locman.leaveOccupiedPlace(bobj);
}
}
@@ -67,6 +67,9 @@ public class CrowdServer extends PresentsServer
/** Our body manager. */
public static BodyManager bodyman;
/** Our location manager. */
public static LocationManager locman;
/**
* Initializes all of the server services and prepares for operation.
*/
@@ -79,6 +82,7 @@ public class CrowdServer extends PresentsServer
plreg = _plreg;
chatprov = _chatprov;
bodyman = _bodyman;
locman = _locman;
// configure the client manager to use our bits
clmgr.setClientFactory(new ClientFactory() {
@@ -90,6 +94,9 @@ public class CrowdServer extends PresentsServer
}
});
// configure the place registry with the injector
_plreg.setInjector(injector);
// create our body locator
_lookup = createBodyLocator();
}
@@ -155,6 +162,9 @@ public class CrowdServer extends PresentsServer
/** Handles body-related invocation services. */
@Inject protected BodyManager _bodyman;
/** Handles location-related invocation services. */
@Inject protected LocationManager _locman;
/** Provides chat-related invocation services. */
@Inject protected ChatProvider _chatprov;
@@ -0,0 +1,223 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2008 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.server;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.server.ClientManager;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.presents.server.PresentsClient;
import com.threerings.crowd.client.LocationService;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.CrowdCodes;
import com.threerings.crowd.data.LocationCodes;
import com.threerings.crowd.data.Place;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.CrowdServer;
import static com.threerings.crowd.Log.log;
/**
* Handles location-related services.
*/
@Singleton
public class LocationManager
implements LocationProvider, LocationCodes
{
@Inject public LocationManager (InvocationManager invmgr)
{
invmgr.registerDispatcher(new LocationDispatcher(this), CrowdCodes.CROWD_GROUP);
}
// from interface LocationProvider
public void moveTo (ClientObject caller, int placeOid, LocationService.MoveListener listener)
throws InvocationException
{
// do the move and send the response
listener.moveSucceeded(moveTo((BodyObject)caller, placeOid));
}
// from interface LocationProvider
public void leavePlace (ClientObject caller)
{
leaveOccupiedPlace((BodyObject)caller);
}
/**
* Moves the specified body from whatever location they currently occupy to the location
* identified by the supplied place oid.
*
* @return the config object for the new location.
*
* @exception ServiceFaildException thrown if the move was not successful for some reason
* (which will be communicated as an error code in the exception's message data).
*/
public PlaceConfig moveTo (BodyObject source, int placeOid)
throws InvocationException
{
int bodoid = source.getOid();
// make sure the place in question actually exists
PlaceManager pmgr = _plreg.getPlaceManager(placeOid);
if (pmgr == null) {
log.info("Requested to move to non-existent place [who=" + source.who() +
", placeOid=" + placeOid + "].");
throw new InvocationException(NO_SUCH_PLACE);
}
// if they're already in the location they're asking to move to, just give them the config
// because we don't need to update anything in distributed object world
Place place = pmgr.getLocation();
if (place.equals(source.location)) {
log.debug("Going along with client request to move to where they already are " +
"[source=" + source.who() + ", place=" + place + "].");
return pmgr.getConfig();
}
// make sure they have access to the specified place
String errmsg;
if ((errmsg = pmgr.ratifyBodyEntry(source)) != null) {
throw new InvocationException(errmsg);
}
// acquire a lock on the body object to avoid breakage by rapid fire moveTo requests
if (!source.acquireLock("moveToLock")) {
// if we're still locked, a previous moveTo request hasn't been fully processed
throw new InvocationException(MOVE_IN_PROGRESS);
}
// configure the client accordingly if the place uses a custom class loader
PresentsClient client = _clmgr.getClient(source.username);
if (client != null) {
client.setClassLoader(pmgr.getClass().getClassLoader());
}
try {
PlaceObject plobj = pmgr.getPlaceObject();
// the doubly nested try catch is to prevent failure if one or the other of the
// transactions fails to start
plobj.startTransaction();
try {
source.startTransaction();
try {
// remove them from any previous location
leaveOccupiedPlace(source);
// generate a new occinfo record (which will add it to the target location)
pmgr.buildOccupantInfo(source);
// set the body's new location
source.willEnterPlace(place, plobj);
// add the body oid to the place object's occupant list
plobj.addToOccupants(bodoid);
} finally {
source.commitTransaction();
}
} finally {
plobj.commitTransaction();
}
} finally {
// and finally queue up an event to release the lock once these events are processed
source.releaseLock("moveToLock");
}
return pmgr.getConfig();
}
/**
* Removes the specified body from the place object they currently occupy. Does nothing if the
* body is not currently in a place.
*/
public void leaveOccupiedPlace (BodyObject source)
{
Place oldloc = source.location;
int bodoid = source.getOid();
// nothing to do if they weren't previously in some location
if (oldloc == null) {
return;
}
// remove them from the occupant list
PlaceObject plobj = null;
try {
plobj = (PlaceObject)_omgr.getObject(oldloc.placeOid);
if (plobj != null) {
Integer key = Integer.valueOf(bodoid);
plobj.startTransaction();
try {
// remove their occupant info (which is keyed on oid)
plobj.removeFromOccupantInfo(key);
// and remove them from the occupant list
plobj.removeFromOccupants(bodoid);
} finally {
plobj.commitTransaction();
}
} else {
log.info("Body's prior location no longer around? [boid=" + bodoid +
", place=" + oldloc + "].");
}
} catch (ClassCastException cce) {
log.warning("Body claims to occupy non-PlaceObject!? [boid=" + bodoid +
", place=" + oldloc + ", error=" + cce + "].");
}
// clear out their location
source.didLeavePlace(plobj);
}
/**
* Forcibly moves the specified body object to the new place. This is accomplished by first
* removing the client from their old location and then sending the client a notification,
* instructing it to move to the new location (which it does using the normal moveTo service).
* This has the benefit that the client is removed from their old place regardless of whether
* or not they are cooperating. If they choose to ignore the forced move request, they will
* remain in limbo, unable to do much of anything.
*/
public void moveBody (BodyObject source, Place place)
{
// first remove them from their old place
leaveOccupiedPlace(source);
// then send a forced move notification
LocationSender.forcedMove(source, place.placeOid);
}
@Inject protected RootDObjectManager _omgr;
@Inject protected PlaceRegistry _plreg;
@Inject protected ClientManager _clmgr;
}
@@ -2,7 +2,7 @@
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
// Copyright (C) 2002-2008 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
@@ -21,218 +21,24 @@
package com.threerings.crowd.server;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.presents.server.PresentsClient;
import com.threerings.crowd.client.LocationService;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.LocationCodes;
import com.threerings.crowd.data.Place;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.CrowdServer;
import static com.threerings.crowd.Log.log;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
/**
* This class provides the server end of the location services.
* Defines the server-side of the {@link LocationService}.
*/
public class LocationProvider
implements LocationCodes, InvocationProvider
public interface LocationProvider extends InvocationProvider
{
/**
* Creates a location provider and prepares it for operation.
* Handles a {@link LocationService#leavePlace} request.
*/
public LocationProvider (InvocationManager invmgr, RootDObjectManager omgr, PlaceRegistry plreg)
{
// we'll need these later
_invmgr = invmgr;
_omgr = omgr;
_plreg = plreg;
}
public void leavePlace (ClientObject caller);
/**
* Requests that this client's body be moved to the specified location.
*
* @param caller the client object of the client that invoked this remotely callable method.
* @param place the oid of the place to which the body should be moved.
* @param listener the listener that will be informed of success or failure.
* Handles a {@link LocationService#moveTo} request.
*/
public void moveTo (ClientObject caller, int placeOid, LocationService.MoveListener listener)
throws InvocationException
{
// do the move and send the response
listener.moveSucceeded(moveTo((BodyObject)caller, placeOid));
}
/**
* Requests that we leave our current place and move to nowhere land.
*/
public void leavePlace (ClientObject caller)
throws InvocationException
{
leaveOccupiedPlace((BodyObject)caller);
}
/**
* Moves the specified body from whatever location they currently occupy to the location
* identified by the supplied place oid.
*
* @return the config object for the new location.
*
* @exception ServiceFaildException thrown if the move was not successful for some reason
* (which will be communicated as an error code in the exception's message data).
*/
public PlaceConfig moveTo (BodyObject source, int placeOid)
throws InvocationException
{
int bodoid = source.getOid();
// make sure the place in question actually exists
PlaceManager pmgr = _plreg.getPlaceManager(placeOid);
if (pmgr == null) {
log.info("Requested to move to non-existent place [who=" + source.who() +
", placeOid=" + placeOid + "].");
throw new InvocationException(NO_SUCH_PLACE);
}
// if they're already in the location they're asking to move to, just give them the config
// because we don't need to update anything in distributed object world
Place place = pmgr.getLocation();
if (place.equals(source.location)) {
log.debug("Going along with client request to move to where they already are " +
"[source=" + source.who() + ", place=" + place + "].");
return pmgr.getConfig();
}
// make sure they have access to the specified place
String errmsg;
if ((errmsg = pmgr.ratifyBodyEntry(source)) != null) {
throw new InvocationException(errmsg);
}
// acquire a lock on the body object to avoid breakage by rapid fire moveTo requests
if (!source.acquireLock("moveToLock")) {
// if we're still locked, a previous moveTo request hasn't been fully processed
throw new InvocationException(MOVE_IN_PROGRESS);
}
// configure the client accordingly if the place uses a custom class loader
PresentsClient client = CrowdServer.clmgr.getClient(source.username);
if (client != null) {
client.setClassLoader(_plreg.getClassLoader(pmgr.getConfig()));
}
try {
PlaceObject plobj = pmgr.getPlaceObject();
// the doubly nested try catch is to prevent failure if one or the other of the
// transactions fails to start
plobj.startTransaction();
try {
source.startTransaction();
try {
// remove them from any previous location
leaveOccupiedPlace(source);
// generate a new occinfo record (which will add it to the target location)
pmgr.buildOccupantInfo(source);
// set the body's new location
source.willEnterPlace(place, plobj);
// add the body oid to the place object's occupant list
plobj.addToOccupants(bodoid);
} finally {
source.commitTransaction();
}
} finally {
plobj.commitTransaction();
}
} finally {
// and finally queue up an event to release the lock once these events are processed
source.releaseLock("moveToLock");
}
return pmgr.getConfig();
}
/**
* Removes the specified body from the place object they currently occupy. Does nothing if the
* body is not currently in a place.
*/
public void leaveOccupiedPlace (BodyObject source)
{
Place oldloc = source.location;
int bodoid = source.getOid();
// nothing to do if they weren't previously in some location
if (oldloc == null) {
return;
}
// remove them from the occupant list
PlaceObject plobj = null;
try {
plobj = (PlaceObject)_omgr.getObject(oldloc.placeOid);
if (plobj != null) {
Integer key = Integer.valueOf(bodoid);
plobj.startTransaction();
try {
// remove their occupant info (which is keyed on oid)
plobj.removeFromOccupantInfo(key);
// and remove them from the occupant list
plobj.removeFromOccupants(bodoid);
} finally {
plobj.commitTransaction();
}
} else {
log.info("Body's prior location no longer around? [boid=" + bodoid +
", place=" + oldloc + "].");
}
} catch (ClassCastException cce) {
log.warning("Body claims to occupy non-PlaceObject!? [boid=" + bodoid +
", place=" + oldloc + ", error=" + cce + "].");
}
// clear out their location
source.didLeavePlace(plobj);
}
/**
* Forcibly moves the specified body object to the new place. This is accomplished by first
* removing the client from their old location and then sending the client a notification,
* instructing it to move to the new location (which it does using the normal moveTo service).
* This has the benefit that the client is removed from their old place regardless of whether
* or not they are cooperating. If they choose to ignore the forced move request, they will
* remain in limbo, unable to do much of anything.
*/
public void moveBody (BodyObject source, Place place)
{
// first remove them from their old place
leaveOccupiedPlace(source);
// then send a forced move notification
LocationSender.forcedMove(source, place.placeOid);
}
/** The invocation manager with which we interoperate. */
protected InvocationManager _invmgr;
/** The distributed object manager with which we interoperate. */
protected RootDObjectManager _omgr;
/** The place registry with which we interoperate. */
protected PlaceRegistry _plreg;
public void moveTo (ClientObject caller, int arg1, LocationService.MoveListener arg2)
throws InvocationException;
}
@@ -36,7 +36,6 @@ import com.samskivert.util.StringUtil;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.AccessController;
import com.threerings.presents.dobj.DObject;
import com.threerings.presents.dobj.DObjectManager;
import com.threerings.presents.dobj.DynamicListener;
import com.threerings.presents.dobj.EntryUpdatedEvent;
import com.threerings.presents.dobj.MessageEvent;
@@ -46,9 +45,9 @@ 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.presents.dobj.RootDObjectManager;
import com.threerings.presents.dobj.SetAdapter;
import com.threerings.presents.server.InvocationManager;
import com.threerings.presents.server.PresentsDObjectMgr;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.OccupantInfo;
@@ -168,8 +167,7 @@ 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 an event updating the distributed set with that clone
_plobj.updateOccupantInfo((OccupantInfo)occInfo.clone());
}
@@ -177,7 +175,7 @@ public class PlaceManager
* Called by the place registry after creating this place manager.
*/
public void init (PlaceRegistry registry, InvocationManager invmgr,
DObjectManager omgr, PlaceConfig config)
RootDObjectManager omgr, PlaceConfig config)
{
_registry = registry;
_invmgr = invmgr;
@@ -263,7 +261,7 @@ public class PlaceManager
public void shutdown ()
{
// destroy the object and everything will follow from that
CrowdServer.omgr.destroyObject(_plobj.getOid());
_omgr.destroyObject(_plobj.getOid());
// clear out our services
if (_plobj.speakService != null) {
@@ -349,7 +347,7 @@ public class PlaceManager
// 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 : _omgr.getObject(srcoid);
Object[] args = event.getArgs(), nargs;
if (args == null) {
nargs = new Object[] { source };
@@ -636,7 +634,7 @@ public class PlaceManager
// queue up a shutdown interval, unless we've already got one.
long idlePeriod = idleUnloadPeriod();
if (idlePeriod > 0L && _shutdownInterval == null) {
_shutdownInterval = new Interval((PresentsDObjectMgr)_omgr) {
_shutdownInterval = new Interval(_omgr) {
public void expired () {
log.debug("Unloading idle place '" + where () + "'.");
shutdown();
@@ -693,7 +691,7 @@ public class PlaceManager
protected InvocationManager _invmgr;
/** A distributed object manager for doing dobj stuff. */
protected DObjectManager _omgr;
protected RootDObjectManager _omgr;
/** A reference to the place object that we manage. */
protected PlaceObject _plobj;
@@ -25,6 +25,7 @@ import java.util.Iterator;
import java.util.List;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.samskivert.util.IntMap;
@@ -57,39 +58,32 @@ public class PlaceRegistry
public void invoke (PlaceManager plmgr);
}
/** The location provider used by the place registry to provide location-related invocation
* services. */
public LocationProvider locprov;
/**
* Creates and initializes the place registry. This is called by the server during its
* initialization phase.
*/
@Inject public PlaceRegistry (ShutdownManager shutmgr, InvocationManager invmgr,
RootDObjectManager omgr)
@Inject public PlaceRegistry (ShutdownManager shutmgr)
{
shutmgr.registerShutdowner(this);
// create and register our location provider
locprov = new LocationProvider(invmgr, omgr, this);
invmgr.registerDispatcher(new LocationDispatcher(locprov), CrowdCodes.CROWD_GROUP);
// we'll need these later
_omgr = omgr;
_invmgr = invmgr;
}
/**
* By overriding this method, it is possible to customize the place registry to cause it to
* load the classes associated with a particular place via a custom class loader. That loader
* may enforce restricted privileges or obtain the classes from some special source.
*
* @return the class loader to use when instantiating the {@link PlaceManager} associated with
* the supplied {@link PlaceConfig}. This method <em>must not</em> return null.
* Provides the place registry with access to an injector that it can use to create {@link
* PlaceManager} instances with all dependencies resolved. This is called by the {@link
* CrowdServer} during the server initialization phase.
*/
public ClassLoader getClassLoader (PlaceConfig config)
public void setInjector (Injector injector)
{
return getClass().getClassLoader();
_injector = injector;
}
/**
* Returns the place manager associated with the specified place object id or null if no such
* place exists.
*/
public PlaceManager getPlaceManager (int placeOid)
{
return _pmgrs.get(placeOid);
}
/**
@@ -142,15 +136,6 @@ public class PlaceRegistry
return createPlace(config, null, hook);
}
/**
* Returns the place manager associated with the specified place object id or null if no such
* place exists.
*/
public PlaceManager getPlaceManager (int placeOid)
{
return _pmgrs.get(placeOid);
}
/**
* Returns an enumeration of all of the registered place objects. This should only be accessed
* on the dobjmgr thread and shouldn't be kept around across event dispatches.
@@ -204,12 +189,10 @@ public class PlaceRegistry
throws InstantiationException, InvocationException
{
PlaceManager pmgr = null;
ClassLoader loader = getClassLoader(config);
try {
// create a place manager using the class supplied in the place config
pmgr = (PlaceManager)Class.forName(
config.getManagerClassName(), true, loader).newInstance();
pmgr = createPlaceManager(config);
// if we have delegates, add them
if (delegates != null) {
@@ -226,8 +209,7 @@ public class PlaceRegistry
throw new InstantiationException("Error creating PlaceManager for " + config);
}
// give the manager an opportunity to abort the whole process if it fails any permissions
// checks
// let the manager abort the whole process if it fails any permissions checks
String errmsg = pmgr.checkPermissions();
if (errmsg != null) {
// give the place manager a chance to clean up after its early initialization process
@@ -256,6 +238,20 @@ public class PlaceRegistry
return pmgr;
}
/**
* Creates an instance of a {@link PlaceManager} using the information in the supplied place
* config. Derived classes may wish to specialize this process for certain places for example
* loading user supplied place management code from a special class loader that sandboxes their
* code.
*/
protected PlaceManager createPlaceManager (PlaceConfig config)
throws Exception
{
@SuppressWarnings("unchecked") Class<? extends PlaceManager> clazz =
(Class<? extends PlaceManager>) Class.forName(config.getManagerClassName());
return _injector.getInstance(clazz);
}
/**
* Called by the place manager when it has been shut down.
*/
@@ -273,10 +269,13 @@ public class PlaceRegistry
}
/** The invocation manager with which we operate. */
protected InvocationManager _invmgr;
@Inject protected InvocationManager _invmgr;
/** The distributed object manager with which we operate. */
protected RootDObjectManager _omgr;
@Inject protected RootDObjectManager _omgr;
/** We use this to inject dependencies into place managers that we create. */
protected Injector _injector;
/** A mapping from place object id to place manager. */
protected IntMap<PlaceManager> _pmgrs = IntMaps.newHashIntMap();
@@ -30,10 +30,11 @@ import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Singleton;
import com.threerings.bureau.data.AgentObject;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.server.ShutdownManager;
import com.threerings.bureau.data.AgentObject;
import static com.threerings.bureau.Log.log;
/**
@@ -173,11 +174,11 @@ public class RegistryTester
}
// create or destroy some agents
TestServer.omgr.postRunnable(createOrDestroyAgents);
_omgr.postRunnable(createOrDestroyAgents);
}
// clean up
TestServer.omgr.postRunnable(new Runnable() {
_omgr.postRunnable(new Runnable() {
public void run () {
for (AgentObject obj : _agents) {
TestServer.breg.destroyAgent(obj);
@@ -217,6 +218,8 @@ public class RegistryTester
return obj;
}
@Inject protected RootDObjectManager _omgr;
protected TestServer _server;
protected boolean _stop;
protected Random _rng1;