diff --git a/src/java/com/threerings/bureau/client/Agent.java b/src/java/com/threerings/bureau/client/Agent.java index a76a57e33..c7f0c4b61 100644 --- a/src/java/com/threerings/bureau/client/Agent.java +++ b/src/java/com/threerings/bureau/client/Agent.java @@ -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; } diff --git a/src/java/com/threerings/bureau/client/BureauClient.java b/src/java/com/threerings/bureau/client/BureauClient.java index 8ab8bb623..f37f5bbe4 100644 --- a/src/java/com/threerings/bureau/client/BureauClient.java +++ b/src/java/com/threerings/bureau/client/BureauClient.java @@ -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 () { diff --git a/src/java/com/threerings/bureau/client/BureauDirector.java b/src/java/com/threerings/bureau/client/BureauDirector.java index debf1c1e6..35a6aced5 100644 --- a/src/java/com/threerings/bureau/client/BureauDirector.java +++ b/src/java/com/threerings/bureau/client/BureauDirector.java @@ -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 +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 delegator = new Subscriber() { + public void objectAvailable (AgentObject agentObject) { + BureauDirector.this.objectAvailable(agentObject); + } + public void requestFailed (int oid, ObjectAccessException cause) { + BureauDirector.this.requestFailed(oid, cause); + } + }; + + _subscriber = new SafeSubscriber(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 + * agentObject'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 _agents = new HashIntMap(); + protected IntMap _agents = IntMaps.newHashIntMap(); + protected SafeSubscriber _subscriber; } diff --git a/src/java/com/threerings/bureau/client/BureauService.java b/src/java/com/threerings/bureau/client/BureauService.java index 571aa9264..90ca92b1f 100644 --- a/src/java/com/threerings/bureau/client/BureauService.java +++ b/src/java/com/threerings/bureau/client/BureauService.java @@ -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 destroyAgent diff --git a/src/java/com/threerings/bureau/data/BureauCredentials.java b/src/java/com/threerings/bureau/data/BureauCredentials.java index 87e247985..2a6ddce55 100644 --- a/src/java/com/threerings/bureau/data/BureauCredentials.java +++ b/src/java/com/threerings/bureau/data/BureauCredentials.java @@ -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; } } diff --git a/src/java/com/threerings/bureau/data/BureauMarshaller.java b/src/java/com/threerings/bureau/data/BureauMarshaller.java index 2769f9054..ccbfe78ca 100644 --- a/src/java/com/threerings/bureau/data/BureauMarshaller.java +++ b/src/java/com/threerings/bureau/data/BureauMarshaller.java @@ -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) diff --git a/src/java/com/threerings/bureau/server/BureauDispatcher.java b/src/java/com/threerings/bureau/server/BureauDispatcher.java index fad99ce07..315dbe44b 100644 --- a/src/java/com/threerings/bureau/server/BureauDispatcher.java +++ b/src/java/com/threerings/bureau/server/BureauDispatcher.java @@ -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, diff --git a/src/java/com/threerings/bureau/server/BureauProvider.java b/src/java/com/threerings/bureau/server/BureauProvider.java index ad0b420c2..465daab26 100644 --- a/src/java/com/threerings/bureau/server/BureauProvider.java +++ b/src/java/com/threerings/bureau/server/BureauProvider.java @@ -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. */ diff --git a/src/java/com/threerings/bureau/server/BureauRegistry.java b/src/java/com/threerings/bureau/server/BureauRegistry.java index 2dee3397d..d7a020bad 100644 --- a/src/java/com/threerings/bureau/server/BureauRegistry.java +++ b/src/java/com/threerings/bureau/server/BureauRegistry.java @@ -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 bureauType is used to determine the Launcher to call. + * Registers a command generator for a given type. When an agent is started and no bureaus are + * running, the bureauType is used to determine the CommandGenerator + * instance to call. * @param bureauType the type of bureau that will be launched - * @param launcher the launcher to be used when the bureauType is requested + * @param cmdGenerator the generator to be used for bureaus of bureauType */ - 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 HashMap hashMap () + { + return new HashMap(); + } + + protected String _serverNameAndPort; protected InvocationManager _invmgr; protected RootDObjectManager _omgr; - protected Map _launchers = new HashMap(); - protected Map _bureaus = new HashMap(); + protected Map _generators = hashMap(); + protected Map _bureaus = hashMap(); } diff --git a/tests/build.xml b/tests/build.xml index 0d0d1c496..dd6e1f27e 100644 --- a/tests/build.xml +++ b/tests/build.xml @@ -179,4 +179,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/src/java/com/threerings/bureau/client/TestClient.java b/tests/src/java/com/threerings/bureau/client/TestClient.java new file mode 100644 index 000000000..62af5f916 --- /dev/null +++ b/tests/src/java/com/threerings/bureau/client/TestClient.java @@ -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 _queue = new Queue(); + } + + /** + * 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(); + } + }; + } + +} + diff --git a/tests/src/java/com/threerings/bureau/server/TestServer.java b/tests/src/java/com/threerings/bureau/server/TestServer.java new file mode 100644 index 000000000..73aeeb703 --- /dev/null +++ b/tests/src/java/com/threerings/bureau/server/TestServer.java @@ -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 init 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); + } +}