diff --git a/src/java/com/threerings/crowd/server/CrowdServer.java b/src/java/com/threerings/crowd/server/CrowdServer.java index 94fbdaa56..29b00d724 100644 --- a/src/java/com/threerings/crowd/server/CrowdServer.java +++ b/src/java/com/threerings/crowd/server/CrowdServer.java @@ -26,7 +26,11 @@ import java.util.Iterator; import com.threerings.util.Name; import com.threerings.presents.dobj.RootDObjectManager; +import com.threerings.presents.net.AuthRequest; +import com.threerings.presents.server.ClientFactory; +import com.threerings.presents.server.ClientResolver; import com.threerings.presents.server.InvocationManager; +import com.threerings.presents.server.PresentsClient; import com.threerings.presents.server.PresentsServer; import com.threerings.crowd.Log; @@ -51,11 +55,15 @@ public class CrowdServer extends PresentsServer // do the presents server initialization super.init(); - // configure the client manager to use our client - clmgr.setClientClass(CrowdClient.class); - - // configure the client manager to use our resolver - clmgr.setClientResolverClass(CrowdClientResolver.class); + // configure the client manager to use our bits + clmgr.setClientFactory(new ClientFactory() { + public PresentsClient createClient (AuthRequest areq) { + return new CrowdClient(); + } + public ClientResolver createClientResolver (Name username) { + return new CrowdClientResolver(); + } + }); // configure the dobject manager with our access controller omgr.setDefaultAccessController(CrowdObjectAccess.DEFAULT); diff --git a/src/java/com/threerings/io/Streamer.java b/src/java/com/threerings/io/Streamer.java index 8ca04960d..1d14a2979 100644 --- a/src/java/com/threerings/io/Streamer.java +++ b/src/java/com/threerings/io/Streamer.java @@ -92,7 +92,7 @@ public class Streamer createStreamers(); } - Streamer stream = (Streamer)_streamers.get(target); + Streamer stream = _streamers.get(target); if (stream == null) { // make sure this is a streamable class if (!isStreamable(target)) { @@ -108,11 +108,9 @@ public class Streamer // create our streamer in a privileged block so that it can // introspect on the to be streamed class try { - stream = (Streamer) AccessController.doPrivileged( - new PrivilegedExceptionAction() { - public Object run () - throws IOException - { + stream = AccessController.doPrivileged( + new PrivilegedExceptionAction() { + public Streamer run () throws IOException { return new Streamer(target); } }); @@ -376,7 +374,8 @@ public class Streamer } catch (Exception e) { String errmsg = "Failure reading streamable field " + "[class=" + _target.getName() + - ", field=" + field.getName() + "]"; + ", field=" + field.getName() + + ", error=" + e.getMessage() + "]"; throw (IOException) new IOException(errmsg).initCause(e); } } @@ -460,7 +459,7 @@ public class Streamer */ protected static void createStreamers () { - _streamers = new HashMap(); + _streamers = new HashMap(); // register all of the basic streamers int bscount = BasicStreamers.BSTREAMER_TYPES.length; @@ -495,7 +494,7 @@ public class Streamer /** Contains the mapping from class names to configured streamer * instances. */ - protected static HashMap _streamers; + protected static HashMap _streamers; /** The name of the custom reader method. */ protected static final String READER_METHOD_NAME = "readObject"; diff --git a/src/java/com/threerings/presents/client/Client.java b/src/java/com/threerings/presents/client/Client.java index e28bbd1be..0efac576f 100644 --- a/src/java/com/threerings/presents/client/Client.java +++ b/src/java/com/threerings/presents/client/Client.java @@ -342,6 +342,16 @@ public class Client return stamp - _serverDelta; } + /** + * Returns true if we are in active communication (we may not yet be logged + * on, but we could be trying to log on). + */ + public synchronized boolean isActive () + { + // if we have a communicator, we're doing something + return (_comm != null); + } + /** * Returns true if we are logged on, false if we're not. */ diff --git a/src/java/com/threerings/presents/client/Communicator.java b/src/java/com/threerings/presents/client/Communicator.java index b8c7d534d..cd6b3a4f8 100644 --- a/src/java/com/threerings/presents/client/Communicator.java +++ b/src/java/com/threerings/presents/client/Communicator.java @@ -475,7 +475,8 @@ public class Communicator } break; } catch (IOException ioe) { - if (ioe instanceof ConnectException && ii < ports.length) { + if (ioe instanceof ConnectException && + ii < (ports.length-1)) { _client.reportLogonTribulations( new LogonException( AuthCodes.TRYING_NEXT_PORT, true)); diff --git a/src/java/com/threerings/presents/peer/net/PeerCreds.java b/src/java/com/threerings/presents/peer/net/PeerCreds.java new file mode 100644 index 000000000..c130884d5 --- /dev/null +++ b/src/java/com/threerings/presents/peer/net/PeerCreds.java @@ -0,0 +1,74 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.net; + +import com.samskivert.util.StringUtil; + +import com.threerings.util.Name; + +import com.threerings.presents.net.UsernamePasswordCreds; + +/** + * Used by peer servers in a cluster installation to authenticate with one + * another. + */ +public class PeerCreds extends UsernamePasswordCreds +{ + /** A prefix prepended to the node name used as a peer's username to + * prevent the username from colliding with a normal authenticating user's + * username. We assume that colons are not allowed in a normal username. */ + public static final String PEER_PREFIX = "peer:"; + + /** + * Creates a unique password for the specified node using the supplied + * shared secret. + */ + public static String createPassword (String nodeName, String sharedSecret) + { + return StringUtil.md5hex(nodeName + sharedSecret); + } + + /** + * Creates credentials for the specified peer. + */ + public PeerCreds (String nodeName, String sharedSecret) + { + super(new Name(PEER_PREFIX + nodeName), + createPassword(nodeName, sharedSecret)); + } + + /** + * Used when unserializing an instance from the network. + */ + public PeerCreds () + { + } + + /** + * Returns the node name of this authenticating peer (which does not + * include the {@link #PEER_PREFIX}. + */ + public String getNodeName () + { + return getUsername().toString().substring(PEER_PREFIX.length()); + } +} diff --git a/src/java/com/threerings/presents/peer/server/PeerAuthenticator.java b/src/java/com/threerings/presents/peer/server/PeerAuthenticator.java new file mode 100644 index 000000000..7727493f6 --- /dev/null +++ b/src/java/com/threerings/presents/peer/server/PeerAuthenticator.java @@ -0,0 +1,77 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.server; + +import com.threerings.presents.data.AuthCodes; + +import com.threerings.presents.net.AuthRequest; +import com.threerings.presents.net.AuthResponse; +import com.threerings.presents.net.AuthResponseData; +import com.threerings.presents.server.Authenticator; +import com.threerings.presents.server.net.AuthingConnection; + +import com.threerings.presents.peer.net.PeerCreds; + +import static com.threerings.presents.Log.log; + +/** + * Handles authentication of peer servers and passes non-peer authentication + * requests through to a normal authenticator. + */ +public class PeerAuthenticator extends Authenticator +{ + /** + * Creates an authenticator that will handle peer authentications and pass + * non-peer authentications through to the supplied delegate. + */ + public PeerAuthenticator (PeerManager nodemgr, Authenticator delegate) + { + _peermgr = nodemgr; + _delegate = delegate; + } + + @Override // documentation inherited + public void authenticateConnection (AuthingConnection conn) + { + // if this is a peer server, we check their credentials specially + AuthRequest req = conn.getAuthRequest(); + if (req.getCredentials() instanceof PeerCreds) { + AuthResponse rsp = new AuthResponse(new AuthResponseData()); + PeerCreds pcreds = (PeerCreds)req.getCredentials(); + if (_peermgr.isAuthenticPeer(pcreds)) { + rsp.getData().code = AuthResponseData.SUCCESS; + } else { + log.warning("Received invalid peer auth request? " + + "[creds=" + pcreds + "]."); + rsp.getData().code = AuthCodes.SERVER_ERROR; + } + connectionWasAuthenticated(conn, rsp); + + } else { + // otherwise pass the request on to our delegate + _delegate.authenticateConnection(conn); + } + } + + protected PeerManager _peermgr; + protected Authenticator _delegate; +} diff --git a/src/java/com/threerings/presents/peer/server/PeerClient.java b/src/java/com/threerings/presents/peer/server/PeerClient.java new file mode 100644 index 000000000..bae2bc4ac --- /dev/null +++ b/src/java/com/threerings/presents/peer/server/PeerClient.java @@ -0,0 +1,31 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.server; + +import com.threerings.presents.server.PresentsClient; + +/** + * Manages a peer server connection. + */ +public class PeerClient extends PresentsClient +{ +} diff --git a/src/java/com/threerings/presents/peer/server/PeerClientFactory.java b/src/java/com/threerings/presents/peer/server/PeerClientFactory.java new file mode 100644 index 000000000..555a22fe0 --- /dev/null +++ b/src/java/com/threerings/presents/peer/server/PeerClientFactory.java @@ -0,0 +1,65 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.server; + +import com.threerings.util.Name; + +import com.threerings.presents.net.AuthRequest; +import com.threerings.presents.server.ClientFactory; +import com.threerings.presents.server.ClientResolver; +import com.threerings.presents.server.PresentsClient; + +import com.threerings.presents.peer.net.PeerCreds; + +/** + * Handles resolution of peer servers and passes non-peer resolution requests + * through to a normal factory. + */ +public class PeerClientFactory implements ClientFactory +{ + public PeerClientFactory (ClientFactory delegate) + { + _delegate = delegate; + } + + // documentation inherited from interface ClientFactory + public PresentsClient createClient (AuthRequest areq) + { + if (areq.getCredentials() instanceof PeerCreds) { + return new PeerClient(); + } else { + return _delegate.createClient(areq); + } + } + + // documentation inherited from interface ClientFactory + public ClientResolver createClientResolver (Name username) + { + if (username.toString().startsWith(PeerCreds.PEER_PREFIX)) { + return new PeerClientResolver(); + } else { + return _delegate.createClientResolver(username); + } + } + + protected ClientFactory _delegate; +} diff --git a/src/java/com/threerings/presents/peer/server/PeerClientResolver.java b/src/java/com/threerings/presents/peer/server/PeerClientResolver.java new file mode 100644 index 000000000..fe76ecac2 --- /dev/null +++ b/src/java/com/threerings/presents/peer/server/PeerClientResolver.java @@ -0,0 +1,31 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.server; + +import com.threerings.presents.server.ClientResolver; + +/** + * Handles the resolution of peer server client data. + */ +public class PeerClientResolver extends ClientResolver +{ +} diff --git a/src/java/com/threerings/presents/peer/server/PeerManager.java b/src/java/com/threerings/presents/peer/server/PeerManager.java new file mode 100644 index 000000000..4f9005c9d --- /dev/null +++ b/src/java/com/threerings/presents/peer/server/PeerManager.java @@ -0,0 +1,273 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.server; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.logging.Level; + +import com.samskivert.io.PersistenceException; +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.util.Interval; +import com.samskivert.util.Invoker; + +import com.threerings.presents.client.Client; +import com.threerings.presents.client.ClientObserver; +import com.threerings.presents.server.PresentsServer; + +import com.threerings.presents.peer.net.PeerCreds; +import com.threerings.presents.peer.server.persist.NodeRecord; +import com.threerings.presents.peer.server.persist.NodeRepository; + +import static com.threerings.presents.Log.log; + +/** + * Manages connections to the other nodes in a Presents server cluster. Each + * server maintains a client connection to the other servers and subscribes to + * the {@link NodeObject} of all peer servers and uses those objects to + * communicate cross-node information. + */ +public class PeerManager +{ + /** + * Creates a peer manager which will create a {@link NodeRepository} which + * is used to publish our existence and discover the other nodes. + * + * @param nodeName this node's unique name. + * @param sharedSecret a shared secret used to allow the peers to + * authenticate with one another. + * @param hostName the DNS name of the server running this node. + * @param port the port on which other nodes should connect to us. + * @param conprov used to obtain our JDBC connections. + * @param invoker we will perform all database operations on the supplied + * invoker thread. + */ + public PeerManager ( + String nodeName, String sharedSecret, String hostName, int port, + ConnectionProvider conprov, Invoker invoker) + throws PersistenceException + { + _nodeName = nodeName; + _hostName = hostName; + _port = port; + _sharedSecret = sharedSecret; + _invoker = invoker; + _noderepo = new NodeRepository(conprov); + } + + /** + * Instructs the node manager to load up information about its other nodes + * and attempt to establish connections with those nodes. + */ + public void init () + { + // first register ourselves with the node table + _invoker.postUnit(new Invoker.Unit() { + public boolean invoke () { + NodeRecord record = new NodeRecord(_nodeName, _hostName, _port); + try { + _noderepo.updateNode(record); + } catch (PersistenceException pe) { + log.warning("Failed to register node record " + + "[rec=" + record + ", error=" + pe + "]."); + } + return false; + } + }); + + // then start our peer refresh interval (this need not use a runqueue + // as all it will do is post an invoker unit) + new Interval() { + public void expired () { + refreshPeers(); + } + }.schedule(5000L, 60*1000L); + } + + /** + * Call this when the server is shutting down to give this node a chance to + * cleanly logoff from its peers and remove its record from the nodes + * table. + */ + public void shutdown () + { + // TODO: clear our record from the node table + for (PeerNode peer : _peers.values()) { + peer.shutdown(); + } + } + + /** + * Returns true if the supplied peer credentials match our shared secret. + */ + public boolean isAuthenticPeer (PeerCreds creds) + { + return PeerCreds.createPassword( + creds.getNodeName(), _sharedSecret).equals(creds.getPassword()); + } + + /** + * Reloads the list of peer nodes from our table and refreshes each with a + * call to {@link #refreshPeer}. + */ + protected void refreshPeers () + { + // load up information on our nodes + _invoker.postUnit(new Invoker.Unit() { + public boolean invoke () { + try { + _nodes = _noderepo.loadNodes(); + return true; + } catch (PersistenceException pe) { + log.warning("Failed to load node records: " + pe + "."); + // we'll just try again next time + return false; + } + } + public void handleResult () { + for (NodeRecord record : _nodes) { + if (record.nodeName.equals(_nodeName)) { + continue; + } + try { + refreshPeer(record); + } catch (Exception e) { + log.log(Level.WARNING, "Failure refreshing peer " + + record + ".", e); + } + } + } + protected ArrayList _nodes; + }); + } + + /** + * Ensures that we have a connection to the specified node if it has + * checked in since we last failed to connect. + */ + protected void refreshPeer (NodeRecord record) + { + PeerNode peer = _peers.get(record.nodeName); + if (peer == null) { + _peers.put(record.nodeName, peer = new PeerNode(record)); + } + peer.refresh(record); + } + + /** + * Contains all runtime information for one of our peer nodes. + */ + protected class PeerNode + implements ClientObserver + { + public PeerNode (NodeRecord record) + { + _record = record; + _client = new Client(null, PresentsServer.omgr); + _client.addClientObserver(this); + } + + public void refresh (NodeRecord record) + { + // if the hostname of this node changed, kill our existing client + // connection and connect anew + if (!record.hostName.equals(_record.hostName) && + _client.isActive()) { + _client.logoff(false); + } + + // if our client is active, we're groovy + if (_client.isActive()) { + return; + } + + // if our client hasn't updated its record since we last tried to + // logon, then just chill + if (_lastConnectStamp > record.lastUpdated.getTime()) { + log.fine("Not reconnecting to stale client [record=" + _record + + ", lastTry=" + new Date(_lastConnectStamp) + "]."); + return; + } + + // otherwise configure our client with the right bits and logon + _client.setCredentials( + new PeerCreds(_record.nodeName, _sharedSecret)); + _client.setServer(record.hostName, new int[] { _record.port }); + _client.logon(); + _lastConnectStamp = System.currentTimeMillis(); + } + + public void shutdown () + { + if (_client.isActive()) { + _client.logoff(false); + } + } + + // documentation inherited from interface ClientObserver + public void clientFailedToLogon (Client client, Exception cause) + { + // we'll reconnect at most one minute later in refreshPeers() + log.warning("Peer logon attempt failed " + _record + ": " + cause); + } + + // documentation inherited from interface ClientObserver + public void clientConnectionFailed (Client client, Exception cause) + { + // we'll reconnect at most one minute later in refreshPeers() + log.warning("Peer connection failed " + _record + ": " + cause); + } + + // documentation inherited from interface ClientObserver + public void clientDidLogon (Client client) + { + log.info("Connected to peer " + _record + "."); + } + + // documentation inherited from interface ClientObserver + public void clientObjectDidChange (Client client) + { + } + + // documentation inherited from interface ClientObserver + public boolean clientWillLogoff (Client client) + { + return true; + } + + // documentation inherited from interface ClientObserver + public void clientDidLogoff (Client client) + { + } + + protected NodeRecord _record; + protected Client _client; + protected long _lastConnectStamp; + } + + protected String _nodeName, _hostName, _sharedSecret; + protected int _port; + protected Invoker _invoker; + protected NodeRepository _noderepo; + protected HashMap _peers = new HashMap(); +} diff --git a/src/java/com/threerings/presents/peer/server/persist/NodeRecord.java b/src/java/com/threerings/presents/peer/server/persist/NodeRecord.java new file mode 100644 index 000000000..f3cac8b85 --- /dev/null +++ b/src/java/com/threerings/presents/peer/server/persist/NodeRecord.java @@ -0,0 +1,65 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.server.persist; + +import java.sql.Timestamp; + +import com.samskivert.util.StringUtil; + +/** + * Contains information on an active node in a Presents server cluster. + */ +public class NodeRecord +{ + /** The unique name assigned to this node. */ + public String nodeName; + + /** The DNS name of the server running this node. */ + public String hostName; + + /** The port on which to connect to this node. */ + public int port; + + /** The last time this node has reported in. */ + public Timestamp lastUpdated; + + /** Used to create a blank instance when loading from the database. */ + public NodeRecord () + { + } + + /** Creates a record for the specified node. */ + public NodeRecord (String nodeName, String hostName, int port) + { + this.nodeName = nodeName; + this.hostName = hostName; + this.port = port; + } + + /** + * Generates a string representation of this instance. + */ + public String toString () + { + return StringUtil.fieldsToString(this); + } +} diff --git a/src/java/com/threerings/presents/peer/server/persist/NodeRepository.java b/src/java/com/threerings/presents/peer/server/persist/NodeRepository.java new file mode 100644 index 000000000..645c3b79f --- /dev/null +++ b/src/java/com/threerings/presents/peer/server/persist/NodeRepository.java @@ -0,0 +1,97 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.peer.server.persist; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; + +import com.samskivert.io.PersistenceException; + +import com.samskivert.jdbc.ConnectionProvider; +import com.samskivert.jdbc.DatabaseLiaison; +import com.samskivert.jdbc.JDBCUtil; +import com.samskivert.jdbc.JORARepository; +import com.samskivert.jdbc.jora.Table; + +/** + * Used to share information on active nodes in a Presents server cluster. + */ +public class NodeRepository extends JORARepository +{ + /** The database identifier used when establishing a database connection. + * This value being nodedb. */ + public static final String NODE_DB_IDENT = "nodedb"; + + /** + * Constructs a new repository with the specified connection provider. + * + * @param conprov the connection provider via which we will obtain our + * database connection. + */ + public NodeRepository (ConnectionProvider conprov) + throws PersistenceException + { + super(conprov, NODE_DB_IDENT); + } + + /** + * Returns a list of all nodes registered in the repository. + */ + public ArrayList loadNodes () + throws PersistenceException + { + return loadAll(_ntable, ""); + } + + /** + * Updates the supplied node record, inserting it into the database if + * necessary. + */ + public void updateNode (NodeRecord record) + throws PersistenceException + { + store(_ntable, record); + } + + @Override // documentation inherited + protected void migrateSchema (Connection conn, DatabaseLiaison liaison) + throws SQLException, PersistenceException + { + JDBCUtil.createTableIfMissing(conn, "NODES", new String[] { + "NODE_NAME VARCHAR(64) NOT NULL", + "HOST_NAME VARCHAR(64) NOT NULL", + "PORT INTEGER NOT NULL", + "LAST_UPDATED TIMESTAMP NOT NULL", + "PRIMARY KEY (NODE_NAME)", + }, ""); + } + + @Override // documentation inherited + protected void createTables () + { + _ntable = new Table( + NodeRecord.class, "NODES", "NODE_NAME", true); + } + + protected Table _ntable; +} diff --git a/src/java/com/threerings/presents/server/ClientFactory.java b/src/java/com/threerings/presents/server/ClientFactory.java new file mode 100644 index 000000000..26b3e0a9b --- /dev/null +++ b/src/java/com/threerings/presents/server/ClientFactory.java @@ -0,0 +1,55 @@ +// +// $Id$ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/narya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.presents.server; + +import com.threerings.util.Name; + +import com.threerings.presents.data.ClientObject; +import com.threerings.presents.net.AuthRequest; + +/** + * Used to create a {@link PresentsClient} instance to manage an authenticated + * client. + */ +public interface ClientFactory +{ + /** The default client factory. */ + public static ClientFactory DEFAULT = new ClientFactory () { + public PresentsClient createClient (AuthRequest areq) { + return new PresentsClient(); + } + public ClientResolver createClientResolver (Name username) { + return new ClientResolver(); + } + }; + + /** + * Creates an uninitialized client instance for the client that has + * authenticated using the supplied request. + */ + public PresentsClient createClient (AuthRequest areq); + + /** + * Requests a resolver for the client identified by the specified username. + */ + public ClientResolver createClientResolver (Name username); +} diff --git a/src/java/com/threerings/presents/server/ClientManager.java b/src/java/com/threerings/presents/server/ClientManager.java index a6fdaf6c2..4c49aa06d 100644 --- a/src/java/com/threerings/presents/server/ClientManager.java +++ b/src/java/com/threerings/presents/server/ClientManager.java @@ -112,41 +112,21 @@ public class ClientManager } /** - * Instructs the client manager to construct instances of this derived - * class of {@link PresentsClient} to managed newly accepted client - * connections. + * Configures the client manager with a factory for creating {@link + * PresentsClient} and {@link ClientResolver} classes for authenticated + * client connections. */ - public void setClientClass (Class clientClass) + public void setClientFactory (ClientFactory factory) { - // sanity check - if (!PresentsClient.class.isAssignableFrom(clientClass)) { - Log.warning("Requested to use client class that does not " + - "derive from PresentsClient " + - "[class=" + clientClass.getName() + "]."); - return; - } - - // make a note of it - _clientClass = clientClass; + _factory = factory; } /** - * Instructs the client to use instances of this {@link - * ClientResolver} derived class when resolving clients in preparation - * for starting a client session. + * Returns the {@link ClientFactory} currently in use. */ - public void setClientResolverClass (Class clrClass) + public ClientFactory getClientFactory () { - // sanity check - if (!ClientResolver.class.isAssignableFrom(clrClass)) { - Log.warning("Requested to use client resolver class that does " + - "not derive from ClientResolver " + - "[class=" + clrClass.getName() + "]."); - - } else { - // make a note of it - _clrClass = clrClass; - } + return _factory; } /** @@ -172,7 +152,7 @@ public class ClientManager */ public PresentsClient getClient (Name authUsername) { - return (PresentsClient)_usermap.get(authUsername); + return _usermap.get(authUsername); } /** @@ -182,7 +162,7 @@ public class ClientManager */ public ClientObject getClientObject (Name username) { - return (ClientObject)_objmap.get(username); + return _objmap.get(username); } /** @@ -207,7 +187,7 @@ public class ClientManager Name username, final ClientResolutionListener listener) { // look to see if the client object is already resolved - ClientObject clobj = (ClientObject)_objmap.get(username); + ClientObject clobj = _objmap.get(username); if (clobj != null) { clobj.reference(); listener.clientResolved(username, clobj); @@ -215,7 +195,7 @@ public class ClientManager } // look to see if it's currently being resolved - ClientResolver clr = (ClientResolver)_penders.get(username); + ClientResolver clr = _penders.get(username); if (clr != null) { // throw this guy onto the bandwagon clr.addResolutionListener(listener); @@ -225,7 +205,7 @@ public class ClientManager try { // create a client resolver instance which will create our // client object, populate it and notify the listeners - clr = (ClientResolver)_clrClass.newInstance(); + clr = _factory.createClientResolver(username); clr.init(username); clr.addResolutionListener(new ClientResolutionListener() { public void clientResolved (Name username, ClientObject clobj) { @@ -270,7 +250,7 @@ public class ClientManager */ public void releaseClientObject (Name username) { - ClientObject clobj = (ClientObject)_objmap.get(username); + ClientObject clobj = _objmap.get(username); if (clobj == null) { Log.warning("Requested to release unmapped client object " + "[username=" + username + "]."); @@ -312,19 +292,11 @@ public class ClientManager Log.info("Session initiated [username=" + username + ", conn=" + conn + "]."); // create a new client and stick'em in the table - try { - // create a client and start up its session - client = (PresentsClient)_clientClass.newInstance(); - client.startSession(this, creds, conn, rsp.authdata); + client = _factory.createClient(req); + client.startSession(this, creds, conn, rsp.authdata); - // map their client instance - _usermap.put(username, client); - - } catch (Exception e) { - Log.warning("Failed to instantiate client instance to " + - "manage new client connection '" + conn + "'."); - Log.logStackTrace(e); - } + // map their client instance + _usermap.put(username, client); } // map this connection to this client @@ -336,7 +308,7 @@ public class ClientManager Connection conn, IOException fault) { // remove the client from the connection map - PresentsClient client = (PresentsClient)_conmap.remove(conn); + PresentsClient client = _conmap.remove(conn); if (client != null) { Log.info("Unmapped failed client [client=" + client + ", conn=" + conn + ", fault=" + fault + "]."); @@ -356,7 +328,7 @@ public class ClientManager public synchronized void connectionClosed (Connection conn) { // remove the client from the connection map - PresentsClient client = (PresentsClient)_conmap.remove(conn); + PresentsClient client = _conmap.remove(conn); if (client != null) { Log.debug("Unmapped client [client=" + client + ", conn=" + conn + "]."); @@ -405,8 +377,7 @@ public class ClientManager { // remove the client from the username map Credentials creds = client.getCredentials(); - PresentsClient rc = (PresentsClient) - _usermap.remove(creds.getUsername()); + PresentsClient rc = _usermap.remove(creds.getUsername()); // sanity check just because we can if (rc == null) { @@ -426,26 +397,23 @@ public class ClientManager */ protected void flushClients () { - ArrayList victims = null; + ArrayList victims = null; long now = System.currentTimeMillis(); // first build a list of our victims (we can't flush clients // directly while iterating due to risk of a // ConcurrentModificationException) - Iterator iter = _usermap.values().iterator(); - while (iter.hasNext()) { - PresentsClient client = (PresentsClient)iter.next(); + for (PresentsClient client : _usermap.values()) { if (client.checkExpired(now)) { if (victims == null) { - victims = new ArrayList(); + victims = new ArrayList(); } victims.add(client); } } if (victims != null) { - for (int ii = 0; ii < victims.size(); ii++) { - PresentsClient client = (PresentsClient)victims.get(ii); + for (PresentsClient client : victims) { try { Log.info("Client expired, ending session " + "[client=" + client + @@ -495,25 +463,23 @@ public class ClientManager } /** A mapping from auth username to client instances. */ - protected HashMap _usermap = new HashMap(); + protected HashMap _usermap = + new HashMap(); /** A mapping from connections to client instances. */ - protected HashMap _conmap = new HashMap(); + protected HashMap _conmap = + new HashMap(); /** A mapping from usernames to client object instances. */ - protected HashMap _objmap = new HashMap(); + protected HashMap _objmap = + new HashMap(); /** A mapping of pending client resolvers. */ - protected HashMap _penders = new HashMap(); - - /** A set containing the usernames of all locked clients. */ - protected HashSet _locks = new HashSet(); + protected HashMap _penders = + new HashMap(); /** The client class in use. */ - protected Class _clientClass = PresentsClient.class; - - /** The client resolver class in use. */ - protected Class _clrClass = ClientResolver.class; + protected ClientFactory _factory = ClientFactory.DEFAULT; /** A count of how many client objects are currently being resolved. */ protected int _outstandingResolutions; diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java index f41a6f3df..b04bec1c3 100644 --- a/src/java/com/threerings/presents/server/PresentsClient.java +++ b/src/java/com/threerings/presents/server/PresentsClient.java @@ -208,7 +208,8 @@ public class PresentsClient // let the client know that the rug has been yanked out // from under their ass - Object[] args = new Object[] { Integer.valueOf(clobj.getOid()) }; + Object[] args = new Object[] { + Integer.valueOf(clobj.getOid()) }; _clobj.postMessage(ClientObject.CLOBJ_CHANGED, args); // call down to any derived classes