Bureaucracy review updates and hello world test

* Fleshed out client-side Agent class, made abstract
* Delegate the creation of the bureau director to subclasses of BureauClient
* Moved BureauDirector's BureauReceiver and Subscriber implementations to 
  anonymous classes that delegate to protected methods.
* Use SafeSubscriber
* Added new service method for notiyfing the server when the creation of an
  agent fails so that the registry is not perpetually waiting
* Made BureauCredentials more conformant
* Made BureauRegistry respond properly to client logging off
* Got rid of superfluous uses of safeToString
* Changed Launcher to CommandGenerator and now use ProcessBuilder and 
  copyMergedOutput
* Changed bureau process parameters to server name and port instead of URL


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5030 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Jamie Doornbos
2008-04-29 18:34:51 +00:00
parent 938d7f5994
commit f41233c5ac
12 changed files with 558 additions and 128 deletions
@@ -26,10 +26,28 @@ import com.threerings.bureau.data.AgentObject;
/**
* Represents an agent running within a bureau client.
*/
public class Agent
public abstract class Agent
{
/**
* Initializes the Agent with the distributed agent object.
*/
public void init (AgentObject agentObj)
{
_agentObj = agentObj;
}
/**
* Starts the code running in the agent.
*/
public abstract void start ();
/**
* Stops the code running in the agent.
*/
public abstract void stop ();
/**
* The shared agent object.
*/
public AgentObject agentObject;
protected AgentObject _agentObj;
}
@@ -32,7 +32,7 @@ import com.samskivert.util.Config;
/**
* Represents a client embedded in a bureau.
*/
public class BureauClient extends Client
public abstract class BureauClient extends Client
{
/**
* Creates a new client.
@@ -45,14 +45,16 @@ public class BureauClient extends Client
_bureauId = bureauId;
BureauCredentials creds = new BureauCredentials();
BureauCredentials creds = new BureauCredentials(_bureauId);
creds.sessionToken = token;
_creds = creds;
_ctx = createContext();
_director = new BureauDirector(_ctx);
_director = createDirector();
}
protected abstract BureauDirector createDirector ();
protected BureauContext createContext ()
{
return new BureauContext () {
@@ -3,81 +3,31 @@ package com.threerings.bureau.client;
import com.threerings.presents.client.BasicDirector;
import com.threerings.presents.data.ClientObject;
import com.threerings.bureau.data.BureauCodes;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.IntMap;
import com.samskivert.util.IntMaps;
import com.threerings.bureau.data.AgentObject;
import com.threerings.bureau.Log;
import com.threerings.bureau.util.BureauContext;
import com.threerings.presents.client.Client;
import com.threerings.presents.dobj.Subscriber;
import com.threerings.presents.util.SafeSubscriber;
import com.threerings.presents.dobj.ObjectAccessException;
/**
* Allows the server to create and destroy agents on a client.
* @see BureauRegistry
*/
public class BureauDirector extends BasicDirector
implements BureauReceiver, Subscriber<AgentObject>
public abstract class BureauDirector extends BasicDirector
{
/**
* Creates a new BureauDirector.
*/
public BureauDirector (BureauContext ctx)
{
super(ctx);
_ctx = ctx;
}
// from BureauReceiver
public synchronized void createAgent (int agentId)
{
_ctx.getDObjectManager().subscribeToObject(agentId, this);
}
// from BureauReceiver
public synchronized void destroyAgent (int agentId)
{
Agent agent = null;
try {
agent = _agents.remove(agentId);
// TODO: stop the agent somehow
}
catch (Throwable t) {
Log.warning("Could not create agent [id=" + agentId + "]");
Log.logStackTrace(t);
// TODO: failure notification?
return;
}
_ctx.getDObjectManager().unsubscribeFromObject(agentId, this);
doStopAgent(agent);
_bureauService.agentDestroyed(_ctx.getClient(), agentId);
}
// from Subscriber
public synchronized void objectAvailable (AgentObject agentObject)
{
int oid = agentObject.getOid();
try {
Agent agent = new Agent();
agent.agentObject = agentObject;
doStartAgent(agent);
_agents.put(oid, agent);
}
catch (Exception e) {
Log.warning("Could not create agent [obj=" + agentObject + "]");
Log.logStackTrace(e);
// TODO: failure notification?
return;
}
// TODO: post to runqueue?
_bureauService.agentCreated(_ctx.getClient(), oid);
}
// from Subscriber
public synchronized void requestFailed (int oid, ObjectAccessException cause)
{
Log.warning("Could not subscribe to agent [oid=" + oid + "]");
Log.logStackTrace(cause);
}
@Override // from BasicDirector
public void clientDidLogon (Client client)
{
@@ -85,6 +35,79 @@ public class BureauDirector extends BasicDirector
_bureauService.bureauInitialized(_ctx.getClient(), _ctx.getBureauId());
}
/**
* Creates a new agent when the server requests it.
*/
protected synchronized void createAgent (int agentId)
{
Subscriber<AgentObject> delegator = new Subscriber<AgentObject>() {
public void objectAvailable (AgentObject agentObject) {
BureauDirector.this.objectAvailable(agentObject);
}
public void requestFailed (int oid, ObjectAccessException cause) {
BureauDirector.this.requestFailed(oid, cause);
}
};
_subscriber = new SafeSubscriber<AgentObject>(agentId, delegator);
_subscriber.subscribe(_ctx.getDObjectManager());
}
/**
* Destroys an agent at the server's request.
*/
protected synchronized void destroyAgent (int agentId)
{
Agent agent = null;
agent = _agents.remove(agentId);
if (agent == null) {
}
else {
try {
agent.stop();
}
catch (Throwable t) {
Log.warning("Stopping an agent caused an exception");
Log.logStackTrace(t);
}
_subscriber.unsubscribe(_ctx.getDObjectManager());
_bureauService.agentDestroyed(_ctx.getClient(), agentId);
}
}
/**
* Callback for when the a request to subscribe to an object finishes and the object is available.
*/
protected synchronized void objectAvailable (AgentObject agentObject)
{
int oid = agentObject.getOid();
Agent agent;
try {
agent = createAgent(agentObject);
agent.init(agentObject);
agent.start();
}
catch (Throwable t) {
Log.warning("Could not create agent [obj=" + agentObject + "]");
Log.logStackTrace(t);
_bureauService.agentCreationFailed(_ctx.getClient(), oid);
return;
}
_agents.put(oid, agent);
_bureauService.agentCreated(_ctx.getClient(), oid);
}
/**
* Callback for when the a request to subscribe to an object fails.
*/
protected synchronized void requestFailed (int oid, ObjectAccessException cause)
{
Log.warning("Could not subscribe to agent [oid=" + oid + "]");
Log.logStackTrace(cause);
}
@Override // from BasicDirector
protected void registerServices (Client client)
{
@@ -95,8 +118,17 @@ public class BureauDirector extends BasicDirector
// Set up our decoder so we can receive method calls
// from the server
BureauReceiver receiver = new BureauReceiver () {
public void createAgent (int agentId) {
BureauDirector.this.createAgent(agentId);
}
public void destroyAgent (int agentId) {
BureauDirector.this.destroyAgent(agentId);
}
};
client.getInvocationDirector().
registerReceiver(new BureauDecoder(this));
registerReceiver(new BureauDecoder(receiver));
}
@Override // from BasicDirector
@@ -108,21 +140,16 @@ public class BureauDirector extends BasicDirector
}
/**
* Called when the agent object is ready and it is time to run his code.
* Called when it is time to create an Agent. Subclasses should read the
* <code>agentObject</code>'s type and/or properties to determine what kind of Agent to
* create.
* @param agentObj the distributed and object
* @return a new Agent that will govern the distributed object
*/
protected void doStartAgent (Agent agent)
{
}
/**
* Called when the agent object is being destroyed and the client code should stop
* processing.
*/
protected void doStopAgent (Agent agent)
{
}
protected abstract Agent createAgent (AgentObject agentObj);
protected BureauContext _ctx;
protected BureauService _bureauService;
protected HashIntMap<Agent> _agents = new HashIntMap<Agent>();
protected IntMap<Agent> _agents = IntMaps.newHashIntMap();
protected SafeSubscriber<AgentObject> _subscriber;
}
@@ -37,11 +37,17 @@ public interface BureauService extends InvocationService
void bureauInitialized (Client client, String bureauId);
/**
* Notify the server that a previosuly requested agent is not created and ready to use.
* Notify the server that a previosuly requested agent is now created and ready to use.
* @see BureauReceiver#createAgent
*/
void agentCreated (Client client, int agentId);
/**
* Notify the server that a previosuly requested agent could not be created.
* @see BureauReceiver#createAgent
*/
void agentCreationFailed (Client client, int agentId);
/**
* Notify the server that an agent is no longer running. Normally called in response
* to a call to <code>destroyAgent</code>
@@ -24,15 +24,35 @@ package com.threerings.bureau.data;
import com.threerings.presents.net.Credentials;
import com.threerings.util.Name;
/**
* Extends the basic credentials to provide bureau-specific fields.
*/
public class BureauCredentials extends Credentials
{
/**
* The token to pass to the server when logging in. This is usually just passed to the bureau
* on the command line to guard against outside connections being established. */
* on the command line to guard against outside connections being established.
*/
public String sessionToken;
/**
* Creates an empty credentials for streaming. Should not be used directly.
*/
public BureauCredentials ()
{
super(new Name("$$$BUREAU$$$"));
}
/**
* Creates new credentials for a specific bureau.
*/
public BureauCredentials (String bureauId)
{
super(new Name("@@bureau:" + bureauId + "@@"));
}
// inherit documentation - from Object
public String toString ()
{
return super.toString() + ", token=" + sessionToken;
}
}
@@ -47,8 +47,19 @@ public class BureauMarshaller extends InvocationMarshaller
});
}
/** The method id used to dispatch {@link #agentCreationFailed} requests. */
public static final int AGENT_CREATION_FAILED = 2;
// from interface BureauService
public void agentCreationFailed (Client arg1, int arg2)
{
sendRequest(arg1, AGENT_CREATION_FAILED, new Object[] {
Integer.valueOf(arg2)
});
}
/** The method id used to dispatch {@link #agentDestroyed} requests. */
public static final int AGENT_DESTROYED = 2;
public static final int AGENT_DESTROYED = 3;
// from interface BureauService
public void agentDestroyed (Client arg1, int arg2)
@@ -59,7 +70,7 @@ public class BureauMarshaller extends InvocationMarshaller
}
/** The method id used to dispatch {@link #bureauInitialized} requests. */
public static final int BUREAU_INITIALIZED = 3;
public static final int BUREAU_INITIALIZED = 4;
// from interface BureauService
public void bureauInitialized (Client arg1, String arg2)
@@ -63,6 +63,13 @@ public class BureauDispatcher extends InvocationDispatcher
);
return;
case BureauMarshaller.AGENT_CREATION_FAILED:
((BureauProvider)provider).agentCreationFailed(
source,
((Integer)args[0]).intValue()
);
return;
case BureauMarshaller.AGENT_DESTROYED:
((BureauProvider)provider).agentDestroyed(
source,
@@ -37,6 +37,11 @@ public interface BureauProvider extends InvocationProvider
*/
public void agentCreated (ClientObject caller, int arg1);
/**
* Handles a {@link BureauService#agentCreationFailed} request.
*/
public void agentCreationFailed (ClientObject caller, int arg1);
/**
* Handles a {@link BureauService#agentDestroyed} request.
*/
@@ -30,8 +30,11 @@ import com.threerings.bureau.data.AgentObject;
import com.threerings.bureau.data.BureauCodes;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.dobj.RootDObjectManager;
import com.threerings.presents.dobj.ObjectDeathListener;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.server.InvocationManager;
import static com.samskivert.util.StringUtil.safeToString;
import com.samskivert.util.StringUtil;
import com.samskivert.util.ProcessLogger;
import com.threerings.bureau.Log;
@@ -42,31 +45,37 @@ import com.threerings.bureau.Log;
public class BureauRegistry
{
/**
* Defines a way to launch a bureau. Launchers are set by the server on startup and are
* invoked whenever an agent is requested whose bureauType matches the registered type.
* @see registerBureau
* Defines the commands that are responsible for invoking a bureau. Instances are associated to
* bureau types by the server on startup. The instances are used whenever the registry needs to
* launch a bureau for an agent with the assocated bureau type.
* @see setCommandGenerator
*/
public static interface Launcher
public static interface CommandGenerator
{
/**
* Launches a new bureau using the given server connect-back url and other information.
* Called by the registry when it decides a new bureau is needed.
* @param serverUrl the url the bureau should use to connect back to the server
* @param serverNameAndPort the name and port the bureau should use to connect back to the
* server, e.g. server.com:47624
* @param bureauId the id of the bureau being launched
* @param token the token string to use for the credentials when logging in
* @return the builder, ready to launch
*/
Process launch (String serverUrl, String bureauId, String token);
String[] createCommand (
String serverNameAndPort,
String bureauId,
String token);
}
/**
* Creates a new registry, prepared to provide bureau services.
*/
public BureauRegistry (
String serverUrl,
String serverNameAndPort,
InvocationManager invmgr,
RootDObjectManager omgr)
{
_serverUrl = serverUrl;
_serverNameAndPort = serverNameAndPort;
_invmgr = invmgr;
_omgr = omgr;
@@ -74,9 +83,12 @@ public class BureauRegistry
public void bureauInitialized (ClientObject client, String bureauId) {
BureauRegistry.this.bureauInitialized(client, bureauId);
}
public void agentCreated (ClientObject client, int agentId) {
public void agentCreated (ClientObject client, int agentId) {
BureauRegistry.this.agentCreated(client, agentId);
}
public void agentCreationFailed (ClientObject client, int agentId) {
BureauRegistry.this.agentCreationFailed(client, agentId);
}
public void agentDestroyed (ClientObject client, int agentId) {
BureauRegistry.this.agentDestroyed(client, agentId);
}
@@ -88,20 +100,21 @@ public class BureauRegistry
}
/**
* Registers a launcher of a given type. When an agent is started and no bureaus are currently
* running, the <code>bureauType</code> is used to determine the <code>Launcher</code> to call.
* Registers a command generator for a given type. When an agent is started and no bureaus are
* running, the <code>bureauType</code> is used to determine the <code>CommandGenerator</code>
* instance to call.
* @param bureauType the type of bureau that will be launched
* @param launcher the launcher to be used when the <code>bureauType</code> is requested
* @param cmdGenerator the generator to be used for bureaus of <code>bureauType</code>
*/
public void setLauncher (String bureauType, Launcher launcher)
public void setCommandGenerator (String bureauType, CommandGenerator cmdGenerator)
{
if (_launchers.get(bureauType) != null) {
Log.warning("Launcher for type already exists [type=" +
if (_generators.get(bureauType) != null) {
Log.warning("Generator for type already exists [type=" +
bureauType + "]");
return;
}
_launchers.put(bureauType, launcher);
_generators.put(bureauType, cmdGenerator);
}
/**
@@ -113,7 +126,7 @@ public class BureauRegistry
if (bureau != null && bureau.ready()) {
Log.info("Bureau ready, sending createAgent " +
safeToString(agent));
StringUtil.toString(agent));
BureauSender.createAgent(bureau.clientObj, agent.getOid());
// !TODO: is this the right place to register the object?
@@ -127,29 +140,38 @@ public class BureauRegistry
if (bureau == null) {
Launcher launcher = _launchers.get(agent.bureauType);
if (launcher == null) {
Log.warning("Launcher not found for agent's " +
"bureau type " + safeToString(agent));
CommandGenerator generator = _generators.get(agent.bureauType);
if (generator == null) {
Log.warning("CommandGenerator not found for agent's " +
"bureau type " + StringUtil.toString(agent));
return;
}
Log.info("Creating new bureau " +
safeToString(agent) + " " +
safeToString(launcher));
StringUtil.toString(agent) + " " +
StringUtil.toString(generator));
bureau = new Bureau();
bureau.bureauId = agent.bureauId;
try {
bureau.process = launcher.launch(_serverUrl, agent.bureauId, "");
// kick off the bureau's process
ProcessBuilder builder = new ProcessBuilder(
generator.createCommand(
_serverNameAndPort, agent.bureauId, ""));
builder.redirectErrorStream(true);
bureau.process = builder.start();
// log the output of the process and prefix with bureau id
ProcessLogger.copyMergedOutput(
Log.log, bureau.bureauId, bureau.process);
}
catch (Exception e) {
Log.warning("Could not launch process for bureau " +
safeToString(agent));
StringUtil.toString(agent));
Log.logStackTrace(e);
// !TODO: how to hook into caller and keep the other clients from hanging
return;
}
@@ -173,7 +195,7 @@ public class BureauRegistry
return;
}
Log.warning("Destroying agent " + safeToString(agent));
Log.warning("Destroying agent " + StringUtil.toString(agent));
// transition the agent to a new state and perform the effect of the transition
switch (found.state) {
@@ -197,7 +219,7 @@ public class BureauRegistry
case Bureau.STILL_BORN:
Log.warning("Acknowledging a request to destory an agent, but agent " +
"is in state " + found.state + ", ignoring request " +
safeToString(found.agent));
StringUtil.toString(found.agent));
break;
}
@@ -210,16 +232,22 @@ public class BureauRegistry
*/
protected synchronized void bureauInitialized (ClientObject client, String bureauId)
{
Bureau bureau = _bureaus.get(bureauId);
final Bureau bureau = _bureaus.get(bureauId);
if (bureau == null) {
Log.warning("Acknowledging initialization of non-existent bureau " +
safeToString(bureauId));
StringUtil.toString(bureauId));
return;
}
bureau.clientObj = client;
Log.info("Bureau created " + safeToString(bureau) +
bureau.clientObj.addListener(new ObjectDeathListener() {
public void objectDestroyed (ObjectDestroyedEvent e) {
BureauRegistry.this.clientDestroyed(bureau);
}
});
Log.info("Bureau created " + StringUtil.toString(bureau) +
", launching pending agents");
// find all pending agents
@@ -235,7 +263,7 @@ public class BureauRegistry
// create them
for (AgentObject agent : pending) {
Log.info("Creating agent " + safeToString(agent));
Log.info("Creating agent " + StringUtil.toString(agent));
BureauSender.createAgent(bureau.clientObj, agent.getOid());
bureau.agentStates.put(agent, Bureau.STARTED);
}
@@ -268,7 +296,38 @@ public class BureauRegistry
case Bureau.DESTROYED:
Log.warning("Received acknowledgement of the creation of an " +
"agent in state " + found.state + ", ignoring request " +
safeToString(found.agent));
StringUtil.toString(found.agent));
break;
}
found.bureau.summarize();
}
/**
* Callback for when the bureau client acknowledges the creation of an agent.
*/
protected synchronized void agentCreationFailed (ClientObject client, int agentId)
{
FoundAgent found = resolve(client, agentId, "agentCreationFailed");
if (found == null) {
return;
}
switch (found.state) {
case Bureau.STARTED:
found.bureau.agentStates.remove(found.agent);
break;
case Bureau.STILL_BORN:
found.bureau.agentStates.remove(found.agent);
break;
case Bureau.PENDING:
case Bureau.RUNNING:
case Bureau.DESTROYED:
Log.warning("Received acknowledgement of creation failure for " +
"agent in state " + found.state + ", ignoring request " +
StringUtil.toString(found.agent));
break;
}
@@ -296,13 +355,22 @@ public class BureauRegistry
case Bureau.STILL_BORN:
Log.warning("Acknowledging agent destruction, but state is " +
found.state + ", ignoring request " +
safeToString(found.agent));
StringUtil.toString(found.agent));
break;
}
found.bureau.summarize();
}
// TODO: schedule a shutdown event for the bureau if this is the last agent
/**
* Callback for when a client is destroyed.
*/
protected synchronized void clientDestroyed (Bureau bureau)
{
// clean up any agents attached to this bureau
for (AgentObject agent : bureau.agentStates.keySet()) {
_omgr.destroyObject(agent.getOid());
}
}
/**
@@ -319,7 +387,7 @@ public class BureauRegistry
if (!(dobj instanceof AgentObject)) {
Log.warning("Object not an agent in " + resolver +
" " + safeToString(dobj));
" " + StringUtil.toString(dobj));
return null;
}
@@ -327,27 +395,27 @@ public class BureauRegistry
Bureau bureau = _bureaus.get(agent.bureauId);
if (bureau == null) {
Log.warning("Bureau not found for agent in " + resolver +
" " + safeToString(agent));
" " + StringUtil.toString(agent));
return null;
}
if (!bureau.agentStates.containsKey(agent)) {
Log.warning("Bureau does not have agent in " + resolver +
" " + safeToString(agent));
" " + StringUtil.toString(agent));
return null;
}
if (bureau.clientObj == null) {
Log.warning("Bureau not yet connected in " + resolver +
" " + safeToString(agent));
" " + StringUtil.toString(agent));
return null;
}
if (client != null && bureau.clientObj != client) {
Log.warning("Masquerading request in " + resolver +
" " + safeToString(agent) +
" " + safeToString(bureau.clientObj) +
" " + safeToString(client));
" " + StringUtil.toString(agent) +
" " + StringUtil.toString(bureau.clientObj) +
" " + StringUtil.toString(client));
return null;
}
@@ -447,9 +515,16 @@ public class BureauRegistry
}
}
protected String _serverUrl;
// More readable generic map creation
// TODO: add to library or use existing
protected static <K, V> HashMap<K, V> hashMap ()
{
return new HashMap<K, V>();
}
protected String _serverNameAndPort;
protected InvocationManager _invmgr;
protected RootDObjectManager _omgr;
protected Map<String, Launcher> _launchers = new HashMap<String, Launcher>();
protected Map<String, Bureau> _bureaus = new HashMap<String, Bureau>();
protected Map<String, CommandGenerator> _generators = hashMap();
protected Map<String, Bureau> _bureaus = hashMap();
}
+20
View File
@@ -179,4 +179,24 @@
</batchtest>
</junit>
</target>
<target name="bureau-runserver" depends="compile"
description="Run the bureau test server.">
<java fork="true" classname="com.threerings.bureau.server.TestServer">
<classpath refid="classpath"/>
</java>
</target>
<!-- NOTE: this target is launched by the bureau-runserver target, modelling the way bureaus -->
<!-- work. As such it is not currently useful on its own -->
<target name="bureau-runclient" depends="compile"
description="Run the bureau test client.">
<java fork="true" classname="com.threerings.bureau.client.TestClient">
<classpath refid="classpath"/>
<sysproperty key="serverName" value="${serverName}"/>
<sysproperty key="serverPort" value="${serverPort}"/>
<sysproperty key="token" value="${token}"/>
<sysproperty key="bureauId" value="${bureauId}"/>
</java>
</target>
</project>
@@ -0,0 +1,132 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 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.bureau.client;
import com.samskivert.util.OneLineLogFormatter;
import com.threerings.bureau.Log;
import com.samskivert.util.Queue;
import com.samskivert.util.StringUtil;
import com.threerings.bureau.client.BureauDirector;
import com.threerings.bureau.data.AgentObject;
import com.samskivert.util.RunQueue;
/**
* Extends bureau client minimally and provides a static main function to create a client and
* connect to a server given by system properties.
*/
public class TestClient extends BureauClient
{
public static void main (String args[])
throws java.net.MalformedURLException
{
// make log pretty
OneLineLogFormatter.configureDefaultHandler();
// create the client and log on
TestClient client = new TestClient(
System.getProperty("token"),
System.getProperty("bureauId"));
client.setServer(
System.getProperty("serverName"),
new int[] {Integer.parseInt(System.getProperty("serverPort"))});
client.logon();
// run it
client.run();
}
/**
* Implements most basic run queue. Required to instantate a client.
*/
static protected class SimpleRunQueue implements RunQueue
{
public void postRunnable (Runnable r)
{
_queue.append(r);
}
public boolean isDispatchThread ()
{
return _main == Thread.currentThread();
}
public void run ()
{
_main = Thread.currentThread();
while (true) {
Runnable r = _queue.get();
r.run();
}
}
protected Thread _main;
protected Queue<Runnable> _queue = new Queue<Runnable>();
}
/**
* The agent class used by our director. Does not actually load any code, just logs the
* start/stop requests.
*/
static protected class TestAgent extends Agent
{
public void start ()
{
Log.info("Starting agent " + StringUtil.toString(_agentObj));
}
public void stop ()
{
Log.info("Stopping agent " + StringUtil.toString(_agentObj));
}
}
/**
* Constructs a new test client.
*/
protected TestClient (String token, String bureauId)
{
super(token, bureauId, new SimpleRunQueue());
}
/**
* Runs the event loop.
*/
protected void run ()
{
((SimpleRunQueue)_runQueue).run();
}
// overridden - creates a simple director
protected BureauDirector createDirector ()
{
// just use our test agent exclusively - in the real world, the agent created would depend
// on the object's type and/or properties
return new BureauDirector(_ctx) {
public Agent createAgent (AgentObject agentObj) {
return new TestAgent();
}
};
}
}
@@ -0,0 +1,107 @@
//
// $Id$
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 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.bureau.server;
import com.threerings.bureau.Log;
import com.threerings.bureau.data.AgentObject;
import com.threerings.presents.server.PresentsServer;
import com.samskivert.util.OneLineLogFormatter;
import com.samskivert.util.StringUtil;
import java.io.File;
/**
* Extends a presents server to include a bureau registry.
*/
public class TestServer extends PresentsServer
{
/**
* The bureau registry for the server. Will be null until <code>init</code> is called.
*/
public static BureauRegistry breg;
/**
* Creates a new server and runs it.
*/
public static void main (String[] args)
{
// make log pretty
OneLineLogFormatter.configureDefaultHandler();
final TestServer server = new TestServer();
try {
server.init();
// request a new agent
// TODO: more tests here - create a thread that posts different kinds of requests
// and somehow monitors results from client
server.omgr.postRunnable(new Runnable() {
public void run () {
server.createTestAgent();
}
});
server.run();
} catch (Exception e) {
Log.warning("Unable to initialize server.");
Log.logStackTrace(e);
}
}
// inherit documentation - from PresentsServer
public void init ()
throws Exception
{
super.init();
breg = new BureauRegistry("localhost:47624", invmgr, omgr);
breg.setCommandGenerator("test", new BureauRegistry.CommandGenerator() {
public String[] createCommand (
String serverNameAndPort,
String bureauId,
String token) {
int colon = serverNameAndPort.indexOf(':');
String [] cmd = {"ant",
"-DserverName=" + serverNameAndPort.substring(0, colon),
"-DserverPort=" + serverNameAndPort.substring(colon + 1),
"-DbureauId=" + bureauId,
"-Dtoken=" + token,
"bureau-runclient"};
return cmd;
}
});
}
/**
* Requests that the bureau client create an agent.
*/
protected void createTestAgent ()
{
AgentObject obj = new AgentObject();
obj.bureauType = "test";
obj.bureauId = "test";
breg.startAgent(obj);
}
}