diff --git a/build.xml b/build.xml
index 54a5caba9..8ade05959 100644
--- a/build.xml
+++ b/build.xml
@@ -20,6 +20,13 @@
+
+
+
+
+
+
-
+
+
diff --git a/etc/libs-incl.xml b/etc/libs-incl.xml
index 63edc142f..b7465693c 100644
--- a/etc/libs-incl.xml
+++ b/etc/libs-incl.xml
@@ -6,6 +6,7 @@
+
diff --git a/src/java/com/threerings/presents/peer/server/EHCachePeerCoordinator.java b/src/java/com/threerings/presents/peer/server/EHCachePeerCoordinator.java
new file mode 100644
index 000000000..5f64fb58b
--- /dev/null
+++ b/src/java/com/threerings/presents/peer/server/EHCachePeerCoordinator.java
@@ -0,0 +1,202 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2009 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.net.MalformedURLException;
+import java.rmi.Naming;
+import java.rmi.NotBoundException;
+import java.rmi.RemoteException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+import com.samskivert.depot.EHCacheAdapter;
+import com.samskivert.util.Tuple;
+
+import com.threerings.presents.peer.data.NodeObject;
+
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.distribution.CacheManagerPeerProvider;
+import net.sf.ehcache.distribution.CacheManagerPeerProviderFactory;
+import net.sf.ehcache.distribution.CachePeer;
+
+import static com.threerings.presents.Log.log;
+
+/**
+ * An EHCache peer coordinator that obtains its information from the Narya peer system.
+ */
+public class EHCachePeerCoordinator extends CacheManagerPeerProviderFactory
+{
+ /** Must correspond to what's provided to the PeerManagerCacheListener in ehcache.xml. */
+ public static final int RMI_PORT = 40001;
+
+ /** Caches we boldly assume exist on each peer. */
+ public static final String[] EHCACHES = {
+ EHCacheAdapter.EHCACHE_RECORD_CACHE,
+ EHCacheAdapter.EHCACHE_SHORT_KEYSET_CACHE,
+ EHCacheAdapter.EHCACHE_LONG_KEYSET_CACHE,
+ EHCacheAdapter.EHCACHE_RESULT_CACHE
+ };
+
+ public static void initWithPeers (PeerManager peerMan)
+ {
+ if (_instance == null) {
+ log.warning("No provider initialized -- not coordinating Whirled and EHCache peers.");
+ return;
+ }
+ _instance.initWithPeers(peerMan);
+ }
+
+ /** Return our provider, creating it if needed. */
+ @Override
+ public CacheManagerPeerProvider createCachePeerProvider (
+ CacheManager cacheManager, Properties properties)
+ {
+ if (_instance == null) {
+ _instance = new Provider(cacheManager);
+ }
+ return _instance;
+ }
+
+ protected static class Provider implements CacheManagerPeerProvider
+ {
+ public Provider (CacheManager cacheManager)
+ {
+ _cacheMan = cacheManager;
+ }
+
+ public void initWithPeers (PeerManager peerMan)
+ {
+ _peerMan = peerMan;
+ }
+
+ // these are internal to EHCache and I have NO clue why they're in the API
+ public void registerPeer (String rmiUrl) { }
+ public void unregisterPeer (String rmiUrl) { }
+
+ public List> listRemoteCachePeers (Ehcache cache)
+ throws CacheException
+ {
+ if (_peerMan == null) {
+ // the ehcache subsystem has fired up but Whirled is still booting;
+ // we return null here and ehcache will try again
+ return Collections.emptyList();
+ }
+
+ // list the current whirled peers
+ final List result = Lists.newArrayList();
+ final Set nodes = Sets.newHashSet();
+ _peerMan.applyToNodes(new Function() {
+ public Void apply (NodeObject node) {
+ if (node != _peerMan.getNodeObject()) {
+ addCachesForNode(result, node.nodeName);
+ nodes.add(node.nodeName);
+ }
+ return null;
+ }
+ });
+
+ // if any previously known peer is no longer with us, clear out the cache
+ Set> toRemove = Sets.newHashSet();
+ for (Tuple key : _peerCache.keySet()) {
+ if (!nodes.contains(key.left)) {
+ toRemove.add(key);
+ }
+ }
+ for (Tuple key : toRemove) {
+ log.info("Removing EHCache peer: " + key);
+ _peerCache.remove(key);
+ }
+
+ return result;
+ }
+
+ public void init ()
+ {
+ // do nothing
+ }
+
+ public void dispose ()
+ throws CacheException
+ {
+ // do nothing
+ }
+
+ public long getTimeForClusterToForm ()
+ {
+ // this is only used when bootstrapping, which we don't do, but whatever
+ return 10000;
+ }
+
+ protected void addCachesForNode (List result, String nodeName)
+ {
+ String nodeHost = _peerMan.getPeerInternalHostName(nodeName);
+ if (nodeHost == null) {
+ log.warning("Eek, couldn't find the public host name of peer", "node", nodeName);
+ return;
+ }
+ String rmiBase = "//" + nodeHost + ":" + RMI_PORT + "/";
+
+ // for the given node, acquire a RMI handle for each known Depot cache
+ for (String cacheName : EHCACHES) {
+ try {
+ result.add(getCache(nodeName, rmiBase + cacheName));
+ } catch (Exception e) {
+ log.warning("Could not resolve remote peer", "host", nodeHost,
+ "cache", cacheName, e);
+ }
+ }
+ }
+
+ protected CachePeer getCache (String nodeName, String url)
+ throws MalformedURLException, RemoteException, NotBoundException
+ {
+ Tuple key = Tuple.newTuple(nodeName, url);
+ // retrieve the RMI handle for the given peer
+ synchronized(_peerCache) {
+ CachePeer peer = _peerCache.get(key);
+ if (peer == null) {
+ // do the (blocking) lookup and stow away the result
+ log.info("RMI lookup of remote cache", "url", url);
+ peer = (CachePeer) Naming.lookup(url);
+ _peerCache.put(key, peer);
+ }
+ return peer;
+ }
+ }
+
+ protected Map, CachePeer> _peerCache = Maps.newHashMap();
+ protected PeerManager _peerMan;
+ protected CacheManager _cacheMan;
+ }
+
+ protected static Provider _instance;
+}