final CloudnamePath path
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
return CN_PREFIX + SEPARATOR + SEPARATOR + path.join(SEPARATOR);
    private LeaseHandle createTemporary(final CloudnamePath path, final String data) {
                    pathToKv(instancePath.get()), data, session.getId());
            public boolean writeData(final String data) {
                        pathToKv(instancePath.get()), data, session.getId());
    public boolean writeLeaseData(final CloudnamePath path, final String data) {
        return consul.writeSessionData(pathToKv(path), data, session.getId());
    public String readLeaseData(final CloudnamePath path) {
        return consul.readData(pathToKv(path));
    public LeaseHandle createLease(
            final LeaseType type, final CloudnamePath path, final String data) {
        switch (type) {
            case PERMANENT:
                if (consul.createPermanentData(pathToKv(path), data)) {
                    return new LeaseHandle() {
                        @Override
                        public boolean writeData(final String data) {
                            return writeLeaseData(path, data);
String pathToKv(final CloudnamePath path)
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
return path;
CloudnamePath getLeasePath()
final CloudnamePath path
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
final String consulPath = pathToKv(path);
    public void addLeaseListener(final CloudnamePath leaseToObserve, final LeaseListener listener) {
        final ConsulWatch watch = consul.createWatch(pathToKv(leaseToObserve));
        watches.put(listener, watch);
        watch.startWatching(new ConsulWatch.ConsulWatchListener() {
            @Override
            public void created(final String valueName, final String value) {
                final CloudnamePath path = kvNameToCloudnamePath(valueName);
                if (path.equals(leaseToObserve)) {
                    listener.leaseCreated(path, value);
boolean removeLease(final CloudnamePath path)
final String valueName  final String value
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
final CloudnamePath path = kvNameToCloudnamePath(valueName);
                if (path.equals(leaseToObserve)) {
                    listener.dataChanged(path, value);
void changed(final String valueName, final String value)
final String valueName
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
final CloudnamePath path = kvNameToCloudnamePath(valueName);
                if (path.equals(leaseToObserve)) {
                    listener.leaseRemoved(path);
void removed(final String valueName)
final CloudnamePath path  final String data
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
final String zkPath = ZK_ROOT + path.join('/');
    public String readLeaseData(final CloudnamePath path) {
        final String zkPath = ZK_ROOT + path.join('/');
    private CloudnamePath toCloudnamePath(final String zkPath) {
        final String clientPath = zkPath.substring(ZK_ROOT.length());
    public void addLeaseCollectionListener(
        final String zkPath = ZK_ROOT + pathToObserve.join('/');
                            listener.leaseCreated(toCloudnamePath(path), data);
                            listener.dataChanged(toCloudnamePath(path), data);
                            listener.leaseRemoved(toCloudnamePath(path));
                collectionListeners.put(listener, watcher);
    public void addLeaseListener(final CloudnamePath leaseToObserve, final LeaseListener listener) {
            final String parentPath = ZK_ROOT + leaseToObserve.getParent().join('/');
            final String fullPath = ZK_ROOT + leaseToObserve.join('/');
                                listener.leaseCreated(toCloudnamePath(path), data);
                                listener.dataChanged(toCloudnamePath(path), data);
                                listener.leaseRemoved(toCloudnamePath(path));
                leaseListeners.put(listener, watcher);
    public void removeLeaseListener(final LeaseListener listener) {
            final NodeCollectionWatcher collectionWatcher = collectionListeners.get(listener);
            if (collectionWatcher != null) {
                collectionListeners.remove(listener);
                collectionWatcher.shutdown();
boolean writeLeaseData(final CloudnamePath path, final String data)

            final LeaseType type  final CloudnamePath path  final String data
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
if (type == null || path == null || data == null) {
            return null;
LeaseHandle createLease(
            final LeaseType type, final CloudnamePath path, final String data)
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
private AtomicBoolean closed = new AtomicBoolean(false);

                    @Override
                    public boolean writeData(final String data) {
                        if (closed.get()) {
                            LOG.info("Attempt to write data to closed leased handle " + data);
                            return false;
new LeaseHandle()
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
if (closed.get()) {
                            return null;
CloudnamePath getLeasePath()
final CloudnamePath path
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
final String zkPath = ZK_ROOT + path.join('/');
        try {
            final Stat nodeStat = curator.checkExists().forPath(zkPath);
            if (nodeStat != null) {
                curator.delete()
                        .withVersion(nodeStat.getVersion())
                        .forPath(zkPath);
                return true;
            return false;
boolean removeLease(final CloudnamePath path)
final CloudnamePath path
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
return new CloudnamePath(path, "node" + Integer.toHexString(nodeCounter.incrementAndGet()));
CloudnamePath appendUniqueName(final CloudnamePath path)
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
@Override
                    public void leaseCreated(final CloudnamePath path, final String data) {
                        assertThat("Notification belongs to root path",
                                rootLease.isSubpathOf(path), is(true));
                        createNotifications.countDown();
new LeaseListener()
final CloudnamePath path
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
removeNotifications.countDown();
void leaseRemoved(final CloudnamePath path)
final CloudnamePath path  final String data
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
dataNotifications.countDown();
void dataChanged(final CloudnamePath path, final String data)
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
backend.addLeaseListener(permanentB, new LeaseListener() {
            backend.addLeaseListener(permanentC, new LeaseListener() {
            backend.createLease(LeaseType.PERMANENT, permanentA, "Some data that belongs to A");
            backend.createLease(LeaseType.PERMANENT, permanentB, "Some data that belongs to B");
new LeaseListener()
final CloudnamePath path
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
return CN_PREFIX + SEPARATOR + EPHEMERAL_PREFIX + SEPARATOR + path.join(SEPARATOR);
String pathToEphemeralKv(final CloudnamePath path)
final CloudnamePath path
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
return CN_PREFIX + SEPARATOR + PERMANENT_PREFIX + SEPARATOR + path.join(SEPARATOR);
    @Override
    public LeaseHandle createTemporaryLease(final CloudnamePath path, final String data) {
                    pathToEphemeralKv(instancePath.get()), data, session.getId());
            public boolean writeLeaseData(final String data) {
                        pathToEphemeralKv(instancePath.get()), data, session.getId());
    public boolean writeTemporaryLeaseData(final CloudnamePath path, final String data) {
        return consul.writeSessionData(pathToEphemeralKv(path), data, session.getId());
    public String readTemporaryLeaseData(final CloudnamePath path) {
        return consul.readData(pathToEphemeralKv(path));
    public void addTemporaryLeaseListener(
            final CloudnamePath pathToWatch, final LeaseListener listener) {
        final ConsulWatch watch = consul.createWatch(pathToEphemeralKv(pathToWatch));
        watches.put(listener, watch);
        watch.startWatching(new ConsulWatch.ConsulWatchListener() {
            @Override
            public void created(final String valueName, final String value) {
                listener.leaseCreated(kvNameToCloudnamePath(valueName), value);
String pathToPermanentKv(final CloudnamePath path)
final String valueName  final String value
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
listener.dataChanged(kvNameToCloudnamePath(valueName), value);
void changed(final String valueName, final String value)
final String valueName
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
listener.leaseRemoved(kvNameToCloudnamePath(valueName));
void removed(final String valueName)
final LeaseListener listener
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
// Remove watcher
        final ConsulWatch watch = watches.get(listener);
        if (watch != null) {
            watch.stop();
    public boolean createPermanantLease(final CloudnamePath path, final String data) {
        return consul.createPermanentData(pathToPermanentKv(path), data);
void removeTemporaryLeaseListener(final LeaseListener listener)
final CloudnamePath path
cn-consul/src/main/java/org/cloudname/backends/consul/ConsulBackend.java
final String consulPath = pathToPermanentKv(path);
    public boolean writePermanentLeaseData(final CloudnamePath path, final String data) {
        return consul.writePermanentData(pathToPermanentKv(path), data);
boolean removePermanentLease(final CloudnamePath path)
CloudnamePath path  String data
cn-core/src/test/java/org/cloudname/core/BackendManagerTest.java
public boolean writeTemporaryLeaseData(CloudnamePath path, String data) {
            public String readTemporaryLeaseData(CloudnamePath path) {
            public void addTemporaryLeaseListener(CloudnamePath pathToWatch, LeaseListener listener) {
LeaseHandle createTemporaryLease(CloudnamePath path, String data)
LeaseListener listener
cn-core/src/test/java/org/cloudname/core/BackendManagerTest.java
void removeTemporaryLeaseListener(LeaseListener listener)
CloudnamePath path  String data
cn-core/src/test/java/org/cloudname/core/BackendManagerTest.java
public boolean removePermanentLease(CloudnamePath path) {
                return false;
boolean createPermanantLease(CloudnamePath path, String data)
CloudnamePath path  String data
cn-core/src/test/java/org/cloudname/core/BackendManagerTest.java
return false;
boolean writePermanentLeaseData(CloudnamePath path, String data)
final CloudnamePath leasePath
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
synchronized (syncObject) {
            if (temporaryLeases.containsKey(leasePath)) {
                temporaryLeases.remove(leasePath);
                notifyTemporaryObservers(leasePath, LeaseEvent.REMOVED, null);
void removeTemporaryLease(final CloudnamePath leasePath)
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
return Long.toHexString(random.nextLong());
String createRandomInstanceName()

            final CloudnamePath path  final LeaseEvent event  final String data
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
for (final CloudnamePath observedPath : observedTemporaryPaths.keySet()) {
            if (observedPath.isSubpathOf(path)) {
                for (final LeaseListener listener : observedTemporaryPaths.get(observedPath)) {
                    switch (event) {
                        case CREATED:
                            listener.leaseCreated(path, data);
                            break;
                        case REMOVED:
                            listener.leaseRemoved(path);
                            break;
                        case DATA:
                            listener.dataChanged(path, data);
                            break;
                        default:
                            throw new RuntimeException("Don't know how to handle " + event);
void notifyTemporaryObservers(
            final CloudnamePath path, final LeaseEvent event, final String data)
final CloudnamePath path  final String data
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
assert path != null : "Path to lease must be set!";
        assert data != null : "Lease data is required";
        synchronized (syncObject) {
            if (permanentLeases.containsKey(path)) {
                return false;
boolean createPermanantLease(final CloudnamePath path, final String data)
final CloudnamePath path
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
synchronized (syncObject) {
            if (!permanentLeases.containsKey(path)) {
                return false;
boolean removePermanentLease(final CloudnamePath path)
final CloudnamePath path  final String data
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
synchronized (syncObject) {
            if (!permanentLeases.containsKey(path)) {
                return false;
boolean writePermanentLeaseData(final CloudnamePath path, final String data)
final CloudnamePath path
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
if (!permanentLeases.containsKey(path)) {
            return permanentLeases.get(path);
    public boolean writeTemporaryLeaseData(final CloudnamePath path, final String data) {
            if (!temporaryLeases.containsKey(path)) {
            temporaryLeases.put(path, data);
            notifyTemporaryObservers(path, LeaseEvent.DATA, data);
    public String readTemporaryLeaseData(final CloudnamePath path) {
            if (!temporaryLeases.containsKey(path)) {
                return null;
            return temporaryLeases.get(path);
    public LeaseHandle createTemporaryLease(final CloudnamePath path, final String data) {
            final String instanceName = createRandomInstanceName();
            CloudnamePath instancePath = new CloudnamePath(path, instanceName);
            while (temporaryLeases.containsKey(instancePath)) {
                instancePath = new CloudnamePath(path, instanceName);
            temporaryLeases.put(instancePath, data);
            notifyTemporaryObservers(instancePath, LeaseEvent.CREATED, data);
            return new MemoryLeaseHandle(this, instancePath);
    private void regenerateEventsForTemporaryListener(
       temporaryLeases.keySet().forEach((temporaryPath) -> {
               listener.leaseCreated(temporaryPath, temporaryLeases.get(temporaryPath));
    /**
     * Generate created events on permanent leases for newly attached listeners.
     */
    private void regenerateEventsForPermanentListener(
            final CloudnamePath path, final LeaseListener listener) {
        permanentLeases.keySet().forEach((permanentPath) -> {
            if (path.isSubpathOf(permanentPath)) {
                listener.leaseCreated(permanentPath, permanentLeases.get(permanentPath));
String readPermanentLeaseData(final CloudnamePath path)

            final CloudnamePath pathToObserve  final LeaseListener listener
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
Set<LeaseListener> listeners = observedTemporaryPaths.get(pathToObserve);
            if (listeners == null) {
                listeners = new HashSet<>();
void addTemporaryLeaseListener(
            final CloudnamePath pathToObserve, final LeaseListener listener)
final LeaseListener listener
cn-memory/src/main/java/org/cloudname/backends/memory/MemoryBackend.java
synchronized (syncObject) {
            for (final Set<LeaseListener> listeners : observedTemporaryPaths.values()) {
                if (listeners.contains(listener)) {
                    listeners.remove(listener);
                    return;
void removeTemporaryLeaseListener(final LeaseListener listener)
final CloudnamePath path  final String data
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
boolean created = false;
        CloudnamePath tempInstancePath = null;
        String tempZkPath = null;
        while (!created) {
            final long instanceId = random.nextLong();
            tempInstancePath = new CloudnamePath(path, Long.toHexString(instanceId));
            tempZkPath = TEMPORARY_ROOT + tempInstancePath.join('/');
            try {

                curator.create()
                        .creatingParentContainersIfNeeded()
                        .withMode(CreateMode.EPHEMERAL)
                        .forPath(tempZkPath, data.getBytes(Charsets.UTF_8));
                created = true;
LeaseHandle createTemporaryLease(final CloudnamePath path, final String data)
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
private AtomicBoolean closed = new AtomicBoolean(false);

            @Override
            public boolean writeLeaseData(final String data) {
                if (closed.get()) {
                    LOG.info("Attempt to write data to closed leased handle " + data);
                    return false;
new LeaseHandle()
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
if (closed.get()) {
                    return null;
CloudnamePath getLeasePath()
final CloudnamePath path  final String data
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
final String zkPath = TEMPORARY_ROOT + path.join('/');
    public String readTemporaryLeaseData(final CloudnamePath path) {
        final String zkPath = TEMPORARY_ROOT + path.join('/');
    private CloudnamePath toCloudnamePath(final String zkPath, final String pathPrefix) {
        final String clientPath = zkPath.substring(pathPrefix.length());
    public void addTemporaryLeaseListener(
        final String zkPath = TEMPORARY_ROOT + pathToObserve.join('/');
                            listener.leaseCreated(toCloudnamePath(path, TEMPORARY_ROOT), data);
                            listener.dataChanged(toCloudnamePath(path, TEMPORARY_ROOT), data);
                            listener.leaseRemoved(toCloudnamePath(path, TEMPORARY_ROOT));
                clientListeners.put(listener, watcher);
    public void removeTemporaryLeaseListener(final LeaseListener listener) {
        synchronized (syncObject) {
            final NodeCollectionWatcher watcher = clientListeners.get(listener);
            if (watcher != null) {
                clientListeners.remove(listener);
                watcher.shutdown();
boolean writeTemporaryLeaseData(final CloudnamePath path, final String data)
final CloudnamePath path  final String data
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
final String zkPath = PERMANENT_ROOT + path.join('/');
        try {
            curator.sync().forPath(zkPath);
            final Stat nodeStat = curator.checkExists().forPath(zkPath);
            if (nodeStat == null) {
                curator.create()
                        .creatingParentContainersIfNeeded()
                        .forPath(zkPath, data.getBytes(Charsets.UTF_8));
                return true;
boolean createPermanantLease(final CloudnamePath path, final String data)
final CloudnamePath path
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
final String zkPath = PERMANENT_ROOT + path.join('/');
        try {
            final Stat nodeStat = curator.checkExists().forPath(zkPath);
            if (nodeStat != null) {
                curator.delete()
                        .withVersion(nodeStat.getVersion())
                        .forPath(zkPath);
                return true;
boolean removePermanentLease(final CloudnamePath path)
final CloudnamePath path  final String data
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
final String zkPath = PERMANENT_ROOT + path.join('/');
        try {
            curator.sync().forPath(zkPath);
            final Stat nodeStat = curator.checkExists().forPath(zkPath);
            if (nodeStat == null) {
                LOG.log(Level.WARNING, "Can't write permanent lease data for lease " + path
                        + " with data " + data + " since the lease doesn't exist");
                return false;
boolean writePermanentLeaseData(final CloudnamePath path, final String data)
final CloudnamePath path
cn-zookeeper/src/main/java/org/cloudname/backends/zookeeper/ZooKeeperBackend.java
final String zkPath = PERMANENT_ROOT + path.join('/');
        try {
            curator.sync().forPath(zkPath);
            final byte[] bytes = curator.getData().forPath(zkPath);
            return new String(bytes, Charsets.UTF_8);
String readPermanentLeaseData(final CloudnamePath path)
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
@Override
                public void run() {
                    final String myData = Long.toHexString(random.nextLong());
                    final LeaseHandle handle = backend.createTemporaryLease(rootLease, myData);
                    assertThat("Got a valid handle back", handle, is(notNullValue()));
                    backend.addTemporaryLeaseListener(rootLease, new LeaseListener() {
                        @Override
                        public void leaseCreated(final CloudnamePath path, final String data) {
                            assertThat("Notification belongs to root path",
                                    rootLease.isSubpathOf(path), is(true));
                            createNotifications.countDown();
new Runnable()
final CloudnamePath path
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
removeNotifications.countDown();
void leaseRemoved(final CloudnamePath path)
final CloudnamePath path  final String data
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
dataNotifications.countDown();
void dataChanged(final CloudnamePath path, final String data)
final LeaseWorker worker : workers
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
worker.writeData();
other
            for (final LeaseWorker worker : workers)
testtools/src/main/java/org/cloudname/testtools/backend/CoreBackendTest.java
backend.addPermanentLeaseListener(permanentB, new LeaseListener() {
            backend.addPermanentLeaseListener(permanentC, new LeaseListener() {
            backend.createPermanantLease(permanentA, "Some data that belongs to A");
            backend.createPermanantLease(permanentB, "Some data that belongs to B");
            // Some might say this is a dirty trick but permanent and temporary leases should not
            // interfere with eachother.
            final LeaseHandle handle = backend.createTemporaryLease(
                    permanentC, "Some data that belongs to C");
            assertThat(handle, is(notNullValue()));
            handle.writeLeaseData("Some other data that belongs to C");
            try {
                handle.close();
new LeaseListener()