You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@curator.apache.org by ra...@apache.org on 2014/02/15 17:38:38 UTC

[1/2] initial commit for Curator REST

Repository: curator
Updated Branches:
  refs/heads/CURATOR-88 [created] 2b2e78582


http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SetDataSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SetDataSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SetDataSpec.java
new file mode 100644
index 0000000..faa83a9
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SetDataSpec.java
@@ -0,0 +1,113 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class SetDataSpec
+{
+    private String path;
+    private boolean watched;
+    private String watchId;
+    private boolean async;
+    private String asyncId;
+    private boolean compressed;
+    private int version;
+    private String data;
+
+    public SetDataSpec()
+    {
+        this("/", false, "", false, "", false, -1, "");
+    }
+
+    public SetDataSpec(String path, boolean watched, String watchId, boolean async, String asyncId, boolean compressed, int version, String data)
+    {
+        this.path = path;
+        this.watched = watched;
+        this.watchId = watchId;
+        this.async = async;
+        this.asyncId = asyncId;
+        this.compressed = compressed;
+        this.version = version;
+        this.data = data;
+    }
+
+    public String getData()
+    {
+        return data;
+    }
+
+    public void setData(String data)
+    {
+        this.data = data;
+    }
+
+    public int getVersion()
+    {
+        return version;
+    }
+
+    public void setVersion(int version)
+    {
+        this.version = version;
+    }
+
+    public String getWatchId()
+    {
+        return watchId;
+    }
+
+    public void setWatchId(String watchId)
+    {
+        this.watchId = watchId;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean isWatched()
+    {
+        return watched;
+    }
+
+    public void setWatched(boolean watched)
+    {
+        this.watched = watched;
+    }
+
+    public boolean isAsync()
+    {
+        return async;
+    }
+
+    public void setAsync(boolean async)
+    {
+        this.async = async;
+    }
+
+    public String getAsyncId()
+    {
+        return asyncId;
+    }
+
+    public void setAsyncId(String asyncId)
+    {
+        this.asyncId = asyncId;
+    }
+
+    public boolean isCompressed()
+    {
+        return compressed;
+    }
+
+    public void setCompressed(boolean compressed)
+    {
+        this.compressed = compressed;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/StatusMessage.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/StatusMessage.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/StatusMessage.java
new file mode 100644
index 0000000..9171f94
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/StatusMessage.java
@@ -0,0 +1,65 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class StatusMessage
+{
+    private String type;
+    private String message;
+    private String details;
+    private String sourceId;
+
+    public StatusMessage()
+    {
+        this("", "", "", "");
+    }
+
+    public StatusMessage(String type, String sourceId, String message, String details)
+    {
+        this.type = type;
+        this.sourceId = sourceId;
+        this.message = message;
+        this.details = details;
+    }
+
+    public String getType()
+    {
+        return type;
+    }
+
+    public void setType(String type)
+    {
+        this.type = type;
+    }
+
+    public String getMessage()
+    {
+        return message;
+    }
+
+    public void setMessage(String message)
+    {
+        this.message = message;
+    }
+
+    public String getDetails()
+    {
+        return details;
+    }
+
+    public void setDetails(String details)
+    {
+        this.details = details;
+    }
+
+    public String getSourceId()
+    {
+        return sourceId;
+    }
+
+    public void setSourceId(String sourceId)
+    {
+        this.sourceId = sourceId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 981e405..e600d42 100644
--- a/pom.xml
+++ b/pom.xml
@@ -224,6 +224,7 @@
         <module>curator-examples</module>
         <module>curator-x-discovery</module>
         <module>curator-x-discovery-server</module>
+        <module>curator-x-rest</module>
     </modules>
 
     <dependencyManagement>
@@ -275,6 +276,18 @@
                 <artifactId>curator-x-discovery-server</artifactId>
                 <version>2.4.1-SNAPSHOT</version>
             </dependency>
+
+            <dependency>
+                <groupId>org.codehaus.jackson</groupId>
+                <artifactId>jackson-mapper-asl</artifactId>
+                <version>1.9.2</version>
+            </dependency>
+
+            <dependency>
+                <groupId>javax.ws.rs</groupId>
+                <artifactId>jsr311-api</artifactId>
+                <version>1.1.1</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 


[2/2] git commit: initial commit for Curator REST

Posted by ra...@apache.org.
initial commit for Curator REST


Project: http://git-wip-us.apache.org/repos/asf/curator/repo
Commit: http://git-wip-us.apache.org/repos/asf/curator/commit/2b2e7858
Tree: http://git-wip-us.apache.org/repos/asf/curator/tree/2b2e7858
Diff: http://git-wip-us.apache.org/repos/asf/curator/diff/2b2e7858

Branch: refs/heads/CURATOR-88
Commit: 2b2e7858212def66c50ac9d383e0bbbc88793e4e
Parents: 9f10828
Author: randgalt <ra...@apache.org>
Authored: Sat Feb 15 11:39:22 2014 -0500
Committer: randgalt <ra...@apache.org>
Committed: Sat Feb 15 11:39:22 2014 -0500

----------------------------------------------------------------------
 curator-x-discovery-server/pom.xml              |   1 -
 curator-x-discovery/pom.xml                     |   1 -
 curator-x-rest/pom.xml                          |  47 ++++
 .../curator/x/rest/CuratorRestClasses.java      |  35 +++
 .../curator/x/rest/CuratorRestContext.java      | 132 ++++++++++
 .../curator/x/rest/api/ClientResource.java      | 247 +++++++++++++++++++
 .../apache/curator/x/rest/api/Constants.java    |  70 ++++++
 .../curator/x/rest/api/LeaderResource.java      |  91 +++++++
 .../apache/curator/x/rest/api/LockResource.java |  77 ++++++
 .../curator/x/rest/api/NodeCacheResource.java   | 102 ++++++++
 .../x/rest/api/PathChildrenCacheResource.java   | 128 ++++++++++
 .../api/PersistentEphemeralNodeResource.java    |  68 +++++
 .../x/rest/api/ReadWriteLockResource.java       |  94 +++++++
 .../x/rest/api/RestBackgroundCallback.java      |  32 +++
 .../apache/curator/x/rest/api/RestWatcher.java  |  27 ++
 .../curator/x/rest/api/SemaphoreResource.java   | 103 ++++++++
 .../curator/x/rest/api/SessionResource.java     |  61 +++++
 .../apache/curator/x/rest/details/Closer.java   |   6 +
 .../apache/curator/x/rest/details/Session.java  |  85 +++++++
 .../curator/x/rest/details/SessionManager.java  |  73 ++++++
 .../curator/x/rest/entities/CreateSpec.java     | 114 +++++++++
 .../curator/x/rest/entities/DeleteSpec.java     |  77 ++++++
 .../curator/x/rest/entities/ExistsSpec.java     |  77 ++++++
 .../x/rest/entities/GetChildrenSpec.java        |  89 +++++++
 .../curator/x/rest/entities/GetDataSpec.java    |  89 +++++++
 .../curator/x/rest/entities/LeaderSpec.java     |  38 +++
 .../curator/x/rest/entities/LockSpec.java       |  41 +++
 .../curator/x/rest/entities/NodeCacheSpec.java  |  54 ++++
 .../curator/x/rest/entities/NodeData.java       |  54 ++++
 .../x/rest/entities/PathChildrenCacheSpec.java  |  66 +++++
 .../entities/PersistentEphemeralNodeSpec.java   |  52 ++++
 .../curator/x/rest/entities/SemaphoreSpec.java  |  65 +++++
 .../curator/x/rest/entities/SetDataSpec.java    | 113 +++++++++
 .../curator/x/rest/entities/StatusMessage.java  |  65 +++++
 pom.xml                                         |  13 +
 35 files changed, 2485 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-discovery-server/pom.xml
----------------------------------------------------------------------
diff --git a/curator-x-discovery-server/pom.xml b/curator-x-discovery-server/pom.xml
index 2901abf..a39c04d 100644
--- a/curator-x-discovery-server/pom.xml
+++ b/curator-x-discovery-server/pom.xml
@@ -55,7 +55,6 @@
         <dependency>
             <groupId>javax.ws.rs</groupId>
             <artifactId>jsr311-api</artifactId>
-            <version>1.1.1</version>
         </dependency>
 
         <dependency>

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-discovery/pom.xml
----------------------------------------------------------------------
diff --git a/curator-x-discovery/pom.xml b/curator-x-discovery/pom.xml
index d40b225..9ff2280 100644
--- a/curator-x-discovery/pom.xml
+++ b/curator-x-discovery/pom.xml
@@ -54,7 +54,6 @@
         <dependency>
             <groupId>org.codehaus.jackson</groupId>
             <artifactId>jackson-mapper-asl</artifactId>
-            <version>1.9.2</version>
         </dependency>
 
         <dependency>

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/pom.xml
----------------------------------------------------------------------
diff --git a/curator-x-rest/pom.xml b/curator-x-rest/pom.xml
new file mode 100644
index 0000000..2e26034
--- /dev/null
+++ b/curator-x-rest/pom.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8"?><!--~
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>apache-curator</artifactId>
+        <groupId>org.apache.curator</groupId>
+        <version>2.4.1-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>curator-x-rest</artifactId>
+    <version>2.4.1-SNAPSHOT</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-recipes</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.codehaus.jackson</groupId>
+            <artifactId>jackson-mapper-asl</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>javax.ws.rs</groupId>
+            <artifactId>jsr311-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestClasses.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestClasses.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestClasses.java
new file mode 100644
index 0000000..5b56b46
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestClasses.java
@@ -0,0 +1,35 @@
+package org.apache.curator.x.rest;
+
+import com.google.common.collect.ImmutableList;
+import org.apache.curator.x.rest.api.ClientResource;
+import org.apache.curator.x.rest.api.LeaderResource;
+import org.apache.curator.x.rest.api.LockResource;
+import org.apache.curator.x.rest.api.NodeCacheResource;
+import org.apache.curator.x.rest.api.PathChildrenCacheResource;
+import org.apache.curator.x.rest.api.PersistentEphemeralNodeResource;
+import org.apache.curator.x.rest.api.ReadWriteLockResource;
+import org.apache.curator.x.rest.api.SemaphoreResource;
+import org.apache.curator.x.rest.api.SessionResource;
+import java.util.List;
+
+public class CuratorRestClasses
+{
+    public static List<Class<?>> getClasses()
+    {
+        ImmutableList.Builder<Class<?>> builder = ImmutableList.builder();
+        builder.add(SessionResource.class);
+        builder.add(ClientResource.class);
+        builder.add(LockResource.class);
+        builder.add(SemaphoreResource.class);
+        builder.add(PathChildrenCacheResource.class);
+        builder.add(NodeCacheResource.class);
+        builder.add(LeaderResource.class);
+        builder.add(ReadWriteLockResource.class);
+        builder.add(PersistentEphemeralNodeResource.class);
+        return builder.build();
+    }
+
+    private CuratorRestClasses()
+    {
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestContext.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestContext.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestContext.java
new file mode 100644
index 0000000..ba33fe6
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/CuratorRestContext.java
@@ -0,0 +1,132 @@
+package org.apache.curator.x.rest;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Queues;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.state.ConnectionState;
+import org.apache.curator.framework.state.ConnectionStateListener;
+import org.apache.curator.utils.ThreadUtils;
+import org.apache.curator.x.rest.details.SessionManager;
+import org.apache.curator.x.rest.entities.StatusMessage;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.ObjectWriter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class CuratorRestContext implements Closeable
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final SessionManager sessionManager = new SessionManager();
+    private final ObjectMapper mapper = new ObjectMapper();
+    private final ObjectWriter writer = mapper.writer();
+    private final CuratorFramework client;
+    private final int sessionLengthMs;
+    private final AtomicReference<State> state = new AtomicReference<State>(State.LATENT);
+    private final ScheduledExecutorService executorService = ThreadUtils.newSingleThreadScheduledExecutor("CuratorRestContext");
+    private final ConnectionStateListener connectionStateListener = new ConnectionStateListener()
+    {
+        @Override
+        public void stateChanged(CuratorFramework client, ConnectionState newState)
+        {
+            if ( newState == ConnectionState.LOST )
+            {
+                handleLostConnection();
+            }
+        }
+    };
+    private final BlockingQueue<StatusMessage> messages = Queues.newLinkedBlockingQueue();
+
+    private enum State
+    {
+        LATENT,
+        STARTED,
+        CLOSED
+    }
+
+    public CuratorRestContext(CuratorFramework client, int sessionLengthMs) throws Exception
+    {
+        this.client = client;
+        this.sessionLengthMs = sessionLengthMs;
+    }
+
+    public CuratorFramework getClient()
+    {
+        Preconditions.checkState(state.get() == State.STARTED, "Not started");
+        return client;
+    }
+
+    public SessionManager getSessionManager()
+    {
+        Preconditions.checkState(state.get() == State.STARTED, "Not started");
+        return sessionManager;
+    }
+
+    public void start()
+    {
+        Preconditions.checkState(state.compareAndSet(State.LATENT, State.STARTED), "Already started");
+
+        client.getConnectionStateListenable().addListener(connectionStateListener);
+
+        Runnable runner = new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                checkSessions();
+            }
+        };
+        executorService.scheduleAtFixedRate(runner, sessionLengthMs, sessionLengthMs, TimeUnit.MILLISECONDS);
+    }
+
+    public void pushMessage(StatusMessage message)
+    {
+        messages.add(message);
+    }
+
+    public Collection<StatusMessage> drainMessages()
+    {
+        List<StatusMessage> localMessages = Lists.newArrayList();
+        messages.drainTo(localMessages);
+        return localMessages;
+    }
+
+    private void checkSessions()
+    {
+
+    }
+
+    @Override
+    public void close()
+    {
+        if ( state.compareAndSet(State.STARTED, State.CLOSED) )
+        {
+            client.getConnectionStateListenable().removeListener(connectionStateListener);
+            executorService.shutdownNow();
+            sessionManager.close();
+        }
+    }
+
+    public ObjectMapper getMapper()
+    {
+        return mapper;
+    }
+
+    public ObjectWriter getWriter()
+    {
+        return writer;
+    }
+
+    private void handleLostConnection()
+    {
+        log.warn("Connection lost - closing all REST sessions");
+        sessionManager.deleteAllSessions();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ClientResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ClientResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ClientResource.java
new file mode 100644
index 0000000..bee1b6f
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ClientResource.java
@@ -0,0 +1,247 @@
+package org.apache.curator.x.rest.api;
+
+import com.google.common.base.Joiner;
+import org.apache.curator.framework.api.*;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.entities.CreateSpec;
+import org.apache.curator.x.rest.entities.DeleteSpec;
+import org.apache.curator.x.rest.entities.ExistsSpec;
+import org.apache.curator.x.rest.entities.GetChildrenSpec;
+import org.apache.curator.x.rest.entities.GetDataSpec;
+import org.apache.curator.x.rest.entities.SetDataSpec;
+import org.codehaus.jackson.node.ObjectNode;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.List;
+
+@Path("/curator/v1/client/{session-id}")
+public class ClientResource
+{
+    private final CuratorRestContext context;
+
+    public ClientResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/get-children")
+    public Response getChildren(@PathParam("session-id") String sessionId, final GetChildrenSpec getChildrenSpec) throws Exception
+    {
+        Constants.getSession(context, sessionId);
+
+        Object builder = context.getClient().getChildren();
+        if ( getChildrenSpec.isWatched() )
+        {
+            builder = castBuilder(builder, Watchable.class).usingWatcher(new RestWatcher(context, getChildrenSpec.getWatchId()));
+        }
+
+        if ( getChildrenSpec.isAsync() )
+        {
+            BackgroundCallback backgroundCallback = new RestBackgroundCallback(context, Constants.CLIENT_GET_CHILDREN_ASYNC, getChildrenSpec.getAsyncId())
+            {
+                @Override
+                protected String getMessage(CuratorEvent event)
+                {
+                    List<String> children = event.getChildren();
+                    if ( children != null )
+                    {
+                        return Joiner.on(getChildrenSpec.getAsyncListSeparator()).join(children);
+                    }
+                    return "";
+                }
+            };
+            builder = castBuilder(builder, Backgroundable.class).inBackground(backgroundCallback);
+        }
+
+        Object children = castBuilder(builder, Pathable.class).forPath(getChildrenSpec.getPath());
+        if ( children != null )
+        {
+            return Response.ok(children).build();
+        }
+        return Response.ok().build();
+    }
+
+    @DELETE
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/delete")
+    public Response delete(@PathParam("session-id") String sessionId, final DeleteSpec deleteSpec) throws Exception
+    {
+        Constants.getSession(context, sessionId);
+
+        Object builder = context.getClient().delete();
+        if ( deleteSpec.isGuaranteed() )
+        {
+            builder = castBuilder(builder, DeleteBuilder.class).guaranteed();
+        }
+        builder = castBuilder(builder, Versionable.class).withVersion(deleteSpec.getVersion());
+
+        if ( deleteSpec.isAsync() )
+        {
+            BackgroundCallback backgroundCallback = new RestBackgroundCallback(context, Constants.CLIENT_DELETE_ASYNC, deleteSpec.getAsyncId());
+            builder = castBuilder(builder, Backgroundable.class).inBackground(backgroundCallback);
+        }
+
+        castBuilder(builder, Pathable.class).forPath(deleteSpec.getPath());
+        return Response.ok().build();
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/set-data")
+    public Response setData(@PathParam("session-id") String sessionId, final SetDataSpec setDataSpec) throws Exception
+    {
+        Constants.getSession(context, sessionId);
+
+        Object builder = context.getClient().setData();
+        if ( setDataSpec.isCompressed() )
+        {
+            builder = castBuilder(builder, Compressible.class).compressed();
+        }
+        if ( setDataSpec.isWatched() )
+        {
+            builder = castBuilder(builder, Watchable.class).usingWatcher(new RestWatcher(context, setDataSpec.getWatchId()));
+        }
+        builder = castBuilder(builder, Versionable.class).withVersion(setDataSpec.getVersion());
+
+        if ( setDataSpec.isAsync() )
+        {
+            BackgroundCallback backgroundCallback = new RestBackgroundCallback(context, Constants.CLIENT_SET_DATA_ASYNC, setDataSpec.getAsyncId());
+            builder = castBuilder(builder, Backgroundable.class).inBackground(backgroundCallback);
+        }
+
+        castBuilder(builder, PathAndBytesable.class).forPath(setDataSpec.getPath(), setDataSpec.getData().getBytes());
+        return Response.ok().build();
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/create")
+    public Response create(@PathParam("session-id") String sessionId, final CreateSpec createSpec) throws Exception
+    {
+        Constants.getSession(context, sessionId);
+
+        Object builder = context.getClient().create();
+        if ( createSpec.isCreatingParentsIfNeeded() )
+        {
+            builder = castBuilder(builder, CreateBuilder.class).creatingParentsIfNeeded();
+        }
+        if ( createSpec.isCompressed() )
+        {
+            builder = castBuilder(builder, Compressible.class).compressed();
+        }
+        if ( createSpec.isWithProtection() )
+        {
+            builder = castBuilder(builder, CreateBuilder.class).withProtection();
+        }
+        builder = castBuilder(builder, CreateModable.class).withMode(createSpec.getMode());
+
+        if ( createSpec.isAsync() )
+        {
+            BackgroundCallback backgroundCallback = new RestBackgroundCallback(context, Constants.CLIENT_CREATE_ASYNC, createSpec.getAsyncId());
+            builder = castBuilder(builder, Backgroundable.class).inBackground(backgroundCallback);
+        }
+
+        String returnPath = String.valueOf(castBuilder(builder, PathAndBytesable.class).forPath(createSpec.getPath(), createSpec.getData().getBytes()));
+
+        ObjectNode node = context.getMapper().createObjectNode();
+        node.put("path", returnPath);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/get-data")
+    public Response getData(@PathParam("session-id") String sessionId, final GetDataSpec getDataSpec) throws Exception
+    {
+        Constants.getSession(context, sessionId);
+
+        Object builder = context.getClient().getData();
+        if ( getDataSpec.isWatched() )
+        {
+            builder = castBuilder(builder, Watchable.class).usingWatcher(new RestWatcher(context, getDataSpec.getWatchId()));
+        }
+        if ( getDataSpec.isDecompressed() )
+        {
+            builder = castBuilder(builder, Decompressible.class).decompressed();
+        }
+
+        if ( getDataSpec.isAsync() )
+        {
+            BackgroundCallback backgroundCallback = new RestBackgroundCallback(context, Constants.CLIENT_GET_DATA_ASYNC, getDataSpec.getAsyncId())
+            {
+                @Override
+                protected String getMessage(CuratorEvent event)
+                {
+                    return (event.getData() != null) ? new String(event.getData()) : "";
+                }
+            };
+            castBuilder(builder, Backgroundable.class).inBackground(backgroundCallback);
+        }
+
+        String result = "";
+        Object bytes = castBuilder(builder, Pathable.class).forPath(getDataSpec.getPath());
+        if ( bytes != null )
+        {
+            result = new String((byte[])bytes);
+        }
+
+        ObjectNode node = context.getMapper().createObjectNode();
+        node.put("data", result);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/exists")
+    public Response exists(@PathParam("session-id") String sessionId, final ExistsSpec existsSpec) throws Exception
+    {
+        Constants.getSession(context, sessionId);
+
+        Object builder = context.getClient().checkExists();
+        if ( existsSpec.isWatched() )
+        {
+            builder = castBuilder(builder, Watchable.class).usingWatcher(new RestWatcher(context, existsSpec.getWatchId()));
+        }
+
+        if ( existsSpec.isAsync() )
+        {
+            BackgroundCallback backgroundCallback = new RestBackgroundCallback(context, Constants.CLIENT_EXISTS_ASYNC, existsSpec.getAsyncId());
+            castBuilder(builder, Backgroundable.class).inBackground(backgroundCallback);
+        }
+
+        Object stat = castBuilder(builder, Pathable.class).forPath(existsSpec.getPath());
+        if ( stat != null )
+        {
+            return Response.ok(stat).build();
+        }
+
+        return Response.ok("{}").build();
+    }
+
+    private static <T> T castBuilder(Object createBuilder, Class<T> clazz)
+    {
+        if ( clazz.isAssignableFrom(createBuilder.getClass()) )
+        {
+            return clazz.cast(createBuilder);
+        }
+        throw new WebApplicationException(Response.Status.BAD_REQUEST);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/Constants.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/Constants.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/Constants.java
new file mode 100644
index 0000000..8d21a0f
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/Constants.java
@@ -0,0 +1,70 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.recipes.cache.ChildData;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.NodeData;
+import org.codehaus.jackson.node.ObjectNode;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+
+class Constants
+{
+    static final String CLIENT_CREATE_ASYNC = "client-create-async";
+    static final String CLIENT_GET_DATA_ASYNC = "client-get-data-async";
+    static final String CLIENT_GET_CHILDREN_ASYNC = "client-get-children-async";
+    static final String CLIENT_SET_DATA_ASYNC = "client-set-data-async";
+    static final String CLIENT_EXISTS_ASYNC = "client-exists-async";
+    static final String CLIENT_DELETE_ASYNC = "client-delete-async";
+    static final String WATCH = "watch";
+    static final String PATH_CACHE = "path-cache";
+    static final String NODE_CACHE = "node-cache";
+    static final String LEADER = "leader";
+
+    static ObjectNode makeIdNode(CuratorRestContext context, String id)
+    {
+        ObjectNode node = context.getMapper().createObjectNode();
+        node.put("id", id);
+        return node;
+    }
+
+    static Session getSession(CuratorRestContext context, String sessionId)
+    {
+        Session session = context.getSessionManager().getSession(sessionId);
+        if ( session == null )
+        {
+            throw new WebApplicationException(Response.Status.NOT_FOUND);
+        }
+        return session;
+    }
+
+    static <T> T getThing(Session session, String id, Class<T> clazz)
+    {
+        T thing = session.getThing(id, clazz);
+        if ( thing == null )
+        {
+            throw new WebApplicationException(Response.Status.NOT_FOUND);
+        }
+        return thing;
+    }
+
+    static <T> T deleteThing(Session session, String id, Class<T> clazz)
+    {
+        T thing = session.deleteThing(id, clazz);
+        if ( thing == null )
+        {
+            throw new WebApplicationException(Response.Status.NOT_FOUND);
+        }
+        return thing;
+    }
+
+    private Constants()
+    {
+    }
+
+    static NodeData toNodeData(ChildData c)
+    {
+        String payload = (c.getData() != null) ? new String(c.getData()) : "";
+        return new NodeData(c.getPath(), c.getStat(), payload);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LeaderResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LeaderResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LeaderResource.java
new file mode 100644
index 0000000..be41246
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LeaderResource.java
@@ -0,0 +1,91 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.recipes.leader.LeaderLatch;
+import org.apache.curator.framework.recipes.leader.LeaderLatchListener;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Closer;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.LeaderSpec;
+import org.apache.curator.x.rest.entities.StatusMessage;
+import org.codehaus.jackson.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+
+@Path("/curator/v1/recipes/leader/{session-id}")
+public class LeaderResource
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final CuratorRestContext context;
+
+    public LeaderResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response startLeaderSelection(@PathParam("session-id") String sessionId, final LeaderSpec leaderSpec) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+
+        LeaderLatch leaderLatch = new LeaderLatch(context.getClient(), leaderSpec.getPath(), leaderSpec.getId());
+        leaderLatch.start();
+
+        Closer<LeaderLatch> closer = new Closer<LeaderLatch>()
+        {
+            @Override
+            public void close(LeaderLatch latch)
+            {
+                try
+                {
+                    latch.close();
+                }
+                catch ( IOException e )
+                {
+                    log.error("Could not close left-over leader latch for path: " + leaderSpec.getPath(), e);
+                }
+            }
+        };
+        final String id = session.addThing(leaderLatch, closer);
+
+        LeaderLatchListener listener = new LeaderLatchListener()
+        {
+            @Override
+            public void isLeader()
+            {
+                context.pushMessage(new StatusMessage(Constants.LEADER, id, "true", ""));
+            }
+
+            @Override
+            public void notLeader()
+            {
+                context.pushMessage(new StatusMessage(Constants.LEADER, id, "false", ""));
+            }
+        };
+        leaderLatch.addListener(listener);
+
+        ObjectNode node = Constants.makeIdNode(context, id);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @DELETE
+    @Path("{leader-id}")
+    public Response closeLeader(@PathParam("session-id") String sessionId, @PathParam("leader-id") String leaderId) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        LeaderLatch leaderLatch = Constants.deleteThing(session, leaderId, LeaderLatch.class);
+        leaderLatch.close();
+        return Response.ok().build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LockResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LockResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LockResource.java
new file mode 100644
index 0000000..0e59844
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/LockResource.java
@@ -0,0 +1,77 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Closer;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.LockSpec;
+import org.codehaus.jackson.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.concurrent.TimeUnit;
+
+@Path("/curator/v1/recipes/lock/{session-id}")
+public class LockResource
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final CuratorRestContext context;
+
+    public LockResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response acquireLock(@PathParam("session-id") String sessionId, final LockSpec lockSpec) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        InterProcessSemaphoreMutex lock = new InterProcessSemaphoreMutex(context.getClient(), lockSpec.getPath());
+        if ( !lock.acquire(lockSpec.getMaxWaitMs(), TimeUnit.MILLISECONDS) )
+        {
+            return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
+        }
+
+        Closer<InterProcessSemaphoreMutex> closer = new Closer<InterProcessSemaphoreMutex>()
+        {
+            @Override
+            public void close(InterProcessSemaphoreMutex mutex)
+            {
+                if ( mutex.isAcquiredInThisProcess() )
+                {
+                    try
+                    {
+                        mutex.release();
+                    }
+                    catch ( Exception e )
+                    {
+                        log.error("Could not release left-over lock for path: " + lockSpec.getPath(), e);
+                    }
+                }
+            }
+        };
+        String id = session.addThing(lock, closer);
+        ObjectNode node = Constants.makeIdNode(context, id);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @DELETE
+    @Path("{lock-id}")
+    public Response releaseLock(@PathParam("session-id") String sessionId, @PathParam("lock-id") String lockId) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        InterProcessSemaphoreMutex lock = Constants.deleteThing(session, lockId, InterProcessSemaphoreMutex.class);
+        lock.release();
+        return Response.ok().build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/NodeCacheResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/NodeCacheResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/NodeCacheResource.java
new file mode 100644
index 0000000..fa46548
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/NodeCacheResource.java
@@ -0,0 +1,102 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.recipes.cache.NodeCache;
+import org.apache.curator.framework.recipes.cache.NodeCacheListener;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Closer;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.NodeCacheSpec;
+import org.apache.curator.x.rest.entities.StatusMessage;
+import org.codehaus.jackson.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+
+@Path("/curator/v1/recipes/node-cache/{session-id}")
+public class NodeCacheResource
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final CuratorRestContext context;
+
+    public NodeCacheResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response newCache(@PathParam("session-id") String sessionId, final NodeCacheSpec spec) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+
+        NodeCache cache = new NodeCache(context.getClient(), spec.getPath(), spec.isDataIsCompressed());
+        cache.start(spec.isBuildInitial());
+
+        Closer<NodeCache> closer = new Closer<NodeCache>()
+        {
+            @Override
+            public void close(NodeCache cache)
+            {
+                try
+                {
+                    cache.close();
+                }
+                catch ( IOException e )
+                {
+                    log.error("Could not close left-over NodeCache for path: " + spec.getPath(), e);
+                }
+            }
+        };
+        final String id = session.addThing(cache, closer);
+
+        NodeCacheListener listener = new NodeCacheListener()
+        {
+            @Override
+            public void nodeChanged() throws Exception
+            {
+                context.pushMessage(new StatusMessage(Constants.NODE_CACHE, id, "", ""));
+            }
+        };
+        cache.getListenable().addListener(listener);
+
+        ObjectNode node = Constants.makeIdNode(context, id);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @DELETE
+    @Path("/{cache-id}")
+    public Response deleteCache(@PathParam("session-id") String sessionId, @PathParam("cache-id") String cacheId)
+    {
+        Session session = Constants.getSession(context, sessionId);
+        NodeCache cache = Constants.deleteThing(session, cacheId, NodeCache.class);
+        try
+        {
+            cache.close();
+        }
+        catch ( IOException e )
+        {
+            log.error("Could not close NodeCache id: " + cacheId, e);
+        }
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("/{cache-id}")
+    public Response getCacheData(@PathParam("session-id") String sessionId, @PathParam("cache-id") String cacheId) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        NodeCache cache = Constants.getThing(session, cacheId, NodeCache.class);
+        return Response.ok(Constants.toNodeData(cache.getCurrentData())).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PathChildrenCacheResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PathChildrenCacheResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PathChildrenCacheResource.java
new file mode 100644
index 0000000..f118d55
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PathChildrenCacheResource.java
@@ -0,0 +1,128 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.recipes.cache.ChildData;
+import org.apache.curator.framework.recipes.cache.PathChildrenCache;
+import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
+import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
+import org.apache.curator.utils.ThreadUtils;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Closer;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.PathChildrenCacheSpec;
+import org.apache.curator.x.rest.entities.StatusMessage;
+import org.codehaus.jackson.node.ArrayNode;
+import org.codehaus.jackson.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.IOException;
+
+@Path("/curator/v1/recipes/path-cache/{session-id}")
+public class PathChildrenCacheResource
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final CuratorRestContext context;
+
+    public PathChildrenCacheResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response newCache(@PathParam("session-id") String sessionId, final PathChildrenCacheSpec spec) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+
+        PathChildrenCache cache = new PathChildrenCache(context.getClient(), spec.getPath(), spec.isCacheData(), spec.isDataIsCompressed(), ThreadUtils.newThreadFactory("PathChildrenCacheResource"));
+        cache.start(spec.getStartMode());
+
+        Closer<PathChildrenCache> closer = new Closer<PathChildrenCache>()
+        {
+            @Override
+            public void close(PathChildrenCache cache)
+            {
+                try
+                {
+                    cache.close();
+                }
+                catch ( IOException e )
+                {
+                    log.error("Could not close left-over PathChildrenCache for path: " + spec.getPath(), e);
+                }
+            }
+        };
+        final String id = session.addThing(cache, closer);
+
+        PathChildrenCacheListener listener = new PathChildrenCacheListener()
+        {
+            @Override
+            public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception
+            {
+                context.pushMessage(new StatusMessage(Constants.PATH_CACHE, id, event.getType().name(), ""));
+            }
+        };
+        cache.getListenable().addListener(listener);
+
+        ObjectNode node = Constants.makeIdNode(context, id);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @DELETE
+    @Path("/{cache-id}")
+    public Response deleteCache(@PathParam("session-id") String sessionId, @PathParam("cache-id") String cacheId)
+    {
+        Session session = Constants.getSession(context, sessionId);
+        PathChildrenCache cache = Constants.deleteThing(session, cacheId, PathChildrenCache.class);
+        try
+        {
+            cache.close();
+        }
+        catch ( IOException e )
+        {
+            log.error("Could not close PathChildrenCache id: " + cacheId, e);
+        }
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("/{cache-id}")
+    public Response getCacheData(@PathParam("session-id") String sessionId, @PathParam("cache-id") String cacheId) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        PathChildrenCache cache = Constants.getThing(session, cacheId, PathChildrenCache.class);
+
+        ArrayNode data = context.getMapper().createArrayNode();
+        for ( ChildData c : cache.getCurrentData() )
+        {
+            data.addPOJO(Constants.toNodeData(c));
+        }
+        return Response.ok(context.getWriter().writeValueAsString(data)).build();
+    }
+
+    @GET
+    @Path("/{cache-id}/{path:.*}")
+    public Response getCacheDataForPath(@PathParam("session-id") String sessionId, @PathParam("cache-id") String cacheId, @PathParam("path") String path) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        PathChildrenCache cache = Constants.getThing(session, cacheId, PathChildrenCache.class);
+        ChildData currentData = cache.getCurrentData(path);
+        if ( currentData == null )
+        {
+            return Response.status(Response.Status.NOT_FOUND).build();
+        }
+
+        return Response.ok(Constants.toNodeData(currentData)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PersistentEphemeralNodeResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PersistentEphemeralNodeResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PersistentEphemeralNodeResource.java
new file mode 100644
index 0000000..165d373
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/PersistentEphemeralNodeResource.java
@@ -0,0 +1,68 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.recipes.nodes.PersistentEphemeralNode;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Closer;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.PersistentEphemeralNodeSpec;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Path("/curator/v1/recipes/persistent-ephemeral-node/{session-id}")
+public class PersistentEphemeralNodeResource
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final CuratorRestContext context;
+
+    public PersistentEphemeralNodeResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response start(@PathParam("session-id") String sessionId, final PersistentEphemeralNodeSpec spec) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        PersistentEphemeralNode node = new PersistentEphemeralNode(context.getClient(), spec.getMode(), spec.getPath(), spec.getData().getBytes());
+        node.start();
+
+        Closer<PersistentEphemeralNode> closer = new Closer<PersistentEphemeralNode>()
+        {
+            @Override
+            public void close(PersistentEphemeralNode node)
+            {
+                try
+                {
+                    node.close();
+                }
+                catch ( Exception e )
+                {
+                    log.error("Could not release left-over persistent ephemeral node for path: " + spec.getPath(), e);
+                }
+            }
+        };
+        String id = session.addThing(node, closer);
+        return Response.ok(context.getWriter().writeValueAsString(Constants.makeIdNode(context, id))).build();
+    }
+
+    @DELETE
+    @Path("{node-id}")
+    public Response close(@PathParam("session-id") String sessionId, @PathParam("node-id") String nodeId) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        PersistentEphemeralNode node = Constants.deleteThing(session, nodeId, PersistentEphemeralNode.class);
+        node.close();
+        return Response.ok().build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ReadWriteLockResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ReadWriteLockResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ReadWriteLockResource.java
new file mode 100644
index 0000000..28634d5
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/ReadWriteLockResource.java
@@ -0,0 +1,94 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.recipes.locks.InterProcessMutex;
+import org.apache.curator.framework.recipes.locks.InterProcessReadWriteLock;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Closer;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.LockSpec;
+import org.codehaus.jackson.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.concurrent.TimeUnit;
+
+@Path("/curator/v1/recipes/read-write-lock/{session-id}")
+public class ReadWriteLockResource
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final CuratorRestContext context;
+
+    public ReadWriteLockResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/read")
+    public Response acquireReadLock(@PathParam("session-id") String sessionId, final LockSpec lockSpec) throws Exception
+    {
+        return internalLock(sessionId, lockSpec, false);
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/write")
+    public Response acquireWriteLock(@PathParam("session-id") String sessionId, final LockSpec lockSpec) throws Exception
+    {
+        return internalLock(sessionId, lockSpec, true);
+    }
+
+    private Response internalLock(String sessionId, final LockSpec lockSpec, boolean writeLock) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        InterProcessReadWriteLock lock = new InterProcessReadWriteLock(context.getClient(), lockSpec.getPath());
+        InterProcessMutex actualLock = writeLock ? lock.writeLock() : lock.readLock();
+        if ( !actualLock.acquire(lockSpec.getMaxWaitMs(), TimeUnit.MILLISECONDS) )
+        {
+            return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
+        }
+
+        Closer<InterProcessMutex> closer = new Closer<InterProcessMutex>()
+        {
+            @Override
+            public void close(InterProcessMutex lock)
+            {
+                if ( lock.isAcquiredInThisProcess() )
+                {
+                    try
+                    {
+                        lock.release();
+                    }
+                    catch ( Exception e )
+                    {
+                        log.error("Could not release left-over read/write lock for path: " + lockSpec.getPath(), e);
+                    }
+                }
+            }
+        };
+        String id = session.addThing(actualLock, closer);
+        ObjectNode node = Constants.makeIdNode(context, id);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @DELETE
+    @Path("{lock-id}")
+    public Response releaseLock(@PathParam("session-id") String sessionId, @PathParam("lock-id") String lockId) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        InterProcessMutex lock = Constants.deleteThing(session, lockId, InterProcessMutex.class);
+        lock.release();
+        return Response.ok().build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestBackgroundCallback.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestBackgroundCallback.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestBackgroundCallback.java
new file mode 100644
index 0000000..364b0b8
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestBackgroundCallback.java
@@ -0,0 +1,32 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.api.BackgroundCallback;
+import org.apache.curator.framework.api.CuratorEvent;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.entities.StatusMessage;
+
+class RestBackgroundCallback implements BackgroundCallback
+{
+    private final CuratorRestContext context;
+    private final String type;
+    private final String asyncId;
+
+    RestBackgroundCallback(CuratorRestContext context, String type, String asyncId)
+    {
+        this.context = context;
+        this.type = type;
+        this.asyncId = asyncId;
+    }
+
+    @Override
+    public void processResult(CuratorFramework client, CuratorEvent event) throws Exception
+    {
+        context.pushMessage(new StatusMessage(type, asyncId, getMessage(event), Integer.toString(event.getResultCode())));
+    }
+
+    protected String getMessage(CuratorEvent event)
+    {
+        return String.valueOf(event.getName());
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestWatcher.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestWatcher.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestWatcher.java
new file mode 100644
index 0000000..4f76227
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/RestWatcher.java
@@ -0,0 +1,27 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.entities.StatusMessage;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+
+class RestWatcher implements Watcher
+{
+    private final CuratorRestContext context;
+    private final String watchId;
+
+    RestWatcher(CuratorRestContext context, String watchId)
+    {
+        this.context = context;
+        this.watchId = watchId;
+    }
+
+    @Override
+    public void process(WatchedEvent event)
+    {
+        if ( event.getType() != Event.EventType.None )
+        {
+            context.pushMessage(new StatusMessage(Constants.WATCH, watchId, event.getType().name(), String.valueOf(event.getPath())));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SemaphoreResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SemaphoreResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SemaphoreResource.java
new file mode 100644
index 0000000..9e02d8a
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SemaphoreResource.java
@@ -0,0 +1,103 @@
+package org.apache.curator.x.rest.api;
+
+import com.google.common.collect.Lists;
+import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2;
+import org.apache.curator.framework.recipes.locks.Lease;
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.apache.curator.x.rest.details.Closer;
+import org.apache.curator.x.rest.details.Session;
+import org.apache.curator.x.rest.entities.SemaphoreSpec;
+import org.codehaus.jackson.node.ObjectNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+@Path("/curator/v1/recipes/semaphore/{session-id}")
+public class SemaphoreResource
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final CuratorRestContext context;
+
+    private static class LeasesHolder
+    {
+        final List<Lease> leases;
+
+        private LeasesHolder(Collection<Lease> leases)
+        {
+            this.leases = Lists.newArrayList(leases);
+        }
+    }
+
+    public SemaphoreResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response acquireSemaphore(@PathParam("session-id") String sessionId, final SemaphoreSpec semaphoreSpec) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        final InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(context.getClient(), semaphoreSpec.getPath(), semaphoreSpec.getMaxLeases());
+        final Collection<Lease> leases = semaphore.acquire(semaphoreSpec.getAcquireQty(), semaphoreSpec.getMaxWaitMs(), TimeUnit.MILLISECONDS);
+        if ( leases == null )
+        {
+            return Response.status(Response.Status.SERVICE_UNAVAILABLE).build();
+        }
+
+        Closer<LeasesHolder> closer = new Closer<LeasesHolder>()
+        {
+            @Override
+            public void close(LeasesHolder holder)
+            {
+                try
+                {
+                    semaphore.returnAll(holder.leases);
+                }
+                catch ( Exception e )
+                {
+                    log.error("Could not release left-over semaphore leases for path: " + semaphoreSpec.getPath(), e);
+                }
+            }
+        };
+        String id = session.addThing(new LeasesHolder(leases), closer);
+        ObjectNode node = Constants.makeIdNode(context, id);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @DELETE
+    @Path("{lease-id}/{release-qty}")
+    public Response releaseSemaphore(@PathParam("session-id") String sessionId, @PathParam("lease-id") String leaseId, @PathParam("release-qty") int releaseQty) throws Exception
+    {
+        Session session = Constants.getSession(context, sessionId);
+        LeasesHolder holder = Constants.getThing(session, leaseId, LeasesHolder.class);
+        if ( holder.leases.size() < releaseQty )
+        {
+            return Response.status(Response.Status.NOT_FOUND).build();
+        }
+
+        while ( releaseQty-- > 0 )
+        {
+            Lease lease = holder.leases.remove(0);
+            lease.close();
+        }
+        if ( holder.leases.size() == 0 )
+        {
+            session.deleteThing(leaseId, LeasesHolder.class);
+        }
+
+        return Response.ok().build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SessionResource.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SessionResource.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SessionResource.java
new file mode 100644
index 0000000..1877177
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/api/SessionResource.java
@@ -0,0 +1,61 @@
+package org.apache.curator.x.rest.api;
+
+import org.apache.curator.x.rest.CuratorRestContext;
+import org.codehaus.jackson.node.ObjectNode;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Path("/curator/v1/session")
+public class SessionResource
+{
+    private final CuratorRestContext context;
+
+    public SessionResource(@Context CuratorRestContext context)
+    {
+        this.context = context;
+    }
+
+    @POST
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response newSession() throws Exception
+    {
+        String sessionId = context.getSessionManager().newSession();
+        ObjectNode node = Constants.makeIdNode(context, sessionId);
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+
+    @DELETE
+    @Path("{id}")
+    public Response deleteSession(@PathParam("id") String id)
+    {
+        if ( !context.getSessionManager().deleteSession(id) )
+        {
+            return Response.status(Response.Status.NOT_FOUND).build();
+        }
+        return Response.ok().build();
+    }
+
+    @GET
+    @Path("{id}")
+    @Produces(MediaType.APPLICATION_JSON)
+    public Response pingSession(@PathParam("id") String id) throws Exception
+    {
+        if ( !context.getSessionManager().pingSession(id) )
+        {
+            return Response.status(Response.Status.NOT_FOUND).build();
+        }
+
+        ObjectNode node = context.getMapper().createObjectNode();
+        node.put("state", context.getClient().getState().name());
+        node.putPOJO("messages", context.drainMessages());
+
+        return Response.ok(context.getWriter().writeValueAsString(node)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Closer.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Closer.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Closer.java
new file mode 100644
index 0000000..0436975
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Closer.java
@@ -0,0 +1,6 @@
+package org.apache.curator.x.rest.details;
+
+public interface Closer<T>
+{
+    public void close(T thing);
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Session.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Session.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Session.java
new file mode 100644
index 0000000..9fe1403
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/Session.java
@@ -0,0 +1,85 @@
+package org.apache.curator.x.rest.details;
+
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.Closeable;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+
+public class Session implements Closeable
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final Map<String, Entry> things = Maps.newConcurrentMap();
+    private final AtomicLong lastUseMs = new AtomicLong(System.currentTimeMillis());
+
+    private static class Entry
+    {
+        final Object thing;
+        final Closer closer;
+
+        private Entry(Object thing, Closer closer)
+        {
+            this.thing = thing;
+            this.closer = closer;
+        }
+    }
+
+    public void updateLastUse()
+    {
+        lastUseMs.set(System.currentTimeMillis());
+    }
+
+    public long getLastUseMs()
+    {
+        return lastUseMs.get();
+    }
+
+    @Override
+    public void close()
+    {
+        for ( Map.Entry<String, Entry> mapEntry : things.entrySet() )
+        {
+            Entry entry = mapEntry.getValue();
+            if ( entry.closer != null )
+            {
+                log.debug(String.format("Closing left over thing. Type: %s - Id: %s", entry.thing.getClass(), mapEntry.getKey()));
+                //noinspection unchecked
+                entry.closer.close(entry.thing);    // lack of generics is safe because addThing() is type-safe
+            }
+        }
+    }
+
+    public String addThing(Object thing)
+    {
+        return addThing(thing, null);
+    }
+
+    public <T> String addThing(T thing, Closer<T> closer)
+    {
+        String id = SessionManager.newId();
+        things.put(id, new Entry(thing, closer));
+        return id;
+    }
+
+    public <T> T getThing(String id, Class<T> clazz)
+    {
+        Entry entry = things.get(id);
+        return cast(clazz, entry);
+    }
+
+    public <T> T deleteThing(String id, Class<T> clazz)
+    {
+        Entry entry = things.remove(id);
+        return cast(clazz, entry);
+    }
+
+    private <T> T cast(Class<T> clazz, Entry entry)
+    {
+        if ( entry != null )
+        {
+            return clazz.cast(entry.thing);
+        }
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/SessionManager.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/SessionManager.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/SessionManager.java
new file mode 100644
index 0000000..619bce8
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/details/SessionManager.java
@@ -0,0 +1,73 @@
+package org.apache.curator.x.rest.details;
+
+import com.google.common.collect.Maps;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+
+public class SessionManager implements Closeable
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final Map<String, Session> sessions = Maps.newConcurrentMap();
+
+    public String newSession()
+    {
+        String id = newId();
+        sessions.put(id, new Session());
+        log.debug("Creating session. Id: " + id);
+        return id;
+    }
+
+    static String newId()
+    {
+        return UUID.randomUUID().toString();
+    }
+
+    @Override
+    public void close()
+    {
+        deleteAllSessions();
+    }
+
+    public void deleteAllSessions()
+    {
+        Collection<Session> localSessions = sessions.values();
+        sessions.clear();
+
+        for ( Session session : localSessions )
+        {
+            session.close();
+        }
+    }
+
+    public boolean deleteSession(String id)
+    {
+        Session session = sessions.remove(id);
+        if ( session != null )
+        {
+            log.debug("Deleting session. Id: " + id);
+            session.close();
+            return true;
+        }
+        return false;
+    }
+
+    public boolean pingSession(String id)
+    {
+        return getSession(id) != null;
+    }
+
+    public Session getSession(String id)
+    {
+        Session session = sessions.get(id);
+        if ( session != null )
+        {
+            log.debug("Pinging session. Id: " + id);
+            session.updateLastUse();
+        }
+        return session;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/CreateSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/CreateSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/CreateSpec.java
new file mode 100644
index 0000000..6a52862
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/CreateSpec.java
@@ -0,0 +1,114 @@
+package org.apache.curator.x.rest.entities;
+
+import org.apache.zookeeper.CreateMode;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class CreateSpec
+{
+    private String path;
+    private String data;
+    private CreateMode mode;
+    private boolean async;
+    private String asyncId;
+    private boolean compressed;
+    private boolean creatingParentsIfNeeded;
+    private boolean withProtection;
+
+    public CreateSpec()
+    {
+        this("/", "", CreateMode.PERSISTENT, false, "", false, false, false);
+    }
+
+    public CreateSpec(String path, String data, CreateMode mode, boolean async, String asyncId, boolean compressed, boolean creatingParentsIfNeeded, boolean withProtection)
+    {
+        this.path = path;
+        this.data = data;
+        this.mode = mode;
+        this.async = async;
+        this.asyncId = asyncId;
+        this.compressed = compressed;
+        this.creatingParentsIfNeeded = creatingParentsIfNeeded;
+        this.withProtection = withProtection;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public String getAsyncId()
+    {
+        return asyncId;
+    }
+
+    public void setAsyncId(String asyncId)
+    {
+        this.asyncId = asyncId;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public String getData()
+    {
+        return data;
+    }
+
+    public void setData(String data)
+    {
+        this.data = data;
+    }
+
+    public CreateMode getMode()
+    {
+        return mode;
+    }
+
+    public void setMode(CreateMode mode)
+    {
+        this.mode = mode;
+    }
+
+    public boolean isAsync()
+    {
+        return async;
+    }
+
+    public void setAsync(boolean async)
+    {
+        this.async = async;
+    }
+
+    public boolean isCompressed()
+    {
+        return compressed;
+    }
+
+    public void setCompressed(boolean compressed)
+    {
+        this.compressed = compressed;
+    }
+
+    public boolean isCreatingParentsIfNeeded()
+    {
+        return creatingParentsIfNeeded;
+    }
+
+    public void setCreatingParentsIfNeeded(boolean creatingParentsIfNeeded)
+    {
+        this.creatingParentsIfNeeded = creatingParentsIfNeeded;
+    }
+
+    public boolean isWithProtection()
+    {
+        return withProtection;
+    }
+
+    public void setWithProtection(boolean withProtection)
+    {
+        this.withProtection = withProtection;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/DeleteSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/DeleteSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/DeleteSpec.java
new file mode 100644
index 0000000..322d3de
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/DeleteSpec.java
@@ -0,0 +1,77 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class DeleteSpec
+{
+    private String path;
+    private boolean async;
+    private String asyncId;
+    private boolean guaranteed;
+    private int version;
+
+    public DeleteSpec()
+    {
+        this("/", false, "", false, -1);
+    }
+
+    public DeleteSpec(String path, boolean async, String asyncId, boolean guaranteed, int version)
+    {
+        this.path = path;
+        this.async = async;
+        this.asyncId = asyncId;
+        this.guaranteed = guaranteed;
+        this.version = version;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean isAsync()
+    {
+        return async;
+    }
+
+    public void setAsync(boolean async)
+    {
+        this.async = async;
+    }
+
+    public String getAsyncId()
+    {
+        return asyncId;
+    }
+
+    public void setAsyncId(String asyncId)
+    {
+        this.asyncId = asyncId;
+    }
+
+    public boolean isGuaranteed()
+    {
+        return guaranteed;
+    }
+
+    public void setGuaranteed(boolean guaranteed)
+    {
+        this.guaranteed = guaranteed;
+    }
+
+    public int getVersion()
+    {
+        return version;
+    }
+
+    public void setVersion(int version)
+    {
+        this.version = version;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/ExistsSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/ExistsSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/ExistsSpec.java
new file mode 100644
index 0000000..74905d8
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/ExistsSpec.java
@@ -0,0 +1,77 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class ExistsSpec
+{
+    private String path;
+    private boolean watched;
+    private String watchId;
+    private boolean async;
+    private String asyncId;
+
+    public ExistsSpec()
+    {
+        this("/", false, "", false, "");
+    }
+
+    public ExistsSpec(String path, boolean watched, String watchId, boolean async, String asyncId)
+    {
+        this.path = path;
+        this.watched = watched;
+        this.watchId = watchId;
+        this.async = async;
+        this.asyncId = asyncId;
+    }
+
+    public String getWatchId()
+    {
+        return watchId;
+    }
+
+    public void setWatchId(String watchId)
+    {
+        this.watchId = watchId;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean isWatched()
+    {
+        return watched;
+    }
+
+    public void setWatched(boolean watched)
+    {
+        this.watched = watched;
+    }
+
+    public boolean isAsync()
+    {
+        return async;
+    }
+
+    public void setAsync(boolean async)
+    {
+        this.async = async;
+    }
+
+    public String getAsyncId()
+    {
+        return asyncId;
+    }
+
+    public void setAsyncId(String asyncId)
+    {
+        this.asyncId = asyncId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetChildrenSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetChildrenSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetChildrenSpec.java
new file mode 100644
index 0000000..1f5c46b
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetChildrenSpec.java
@@ -0,0 +1,89 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class GetChildrenSpec
+{
+    private String path;
+    private boolean async;
+    private String asyncId;
+    private String asyncListSeparator;
+    private boolean watched;
+    private String watchId;
+
+    public GetChildrenSpec()
+    {
+        this("/", false, "", ",", false, "");
+    }
+
+    public GetChildrenSpec(String path, boolean async, String asyncId, String asyncListSeparator, boolean watched, String watchId)
+    {
+        this.path = path;
+        this.async = async;
+        this.asyncId = asyncId;
+        this.asyncListSeparator = asyncListSeparator;
+        this.watched = watched;
+        this.watchId = watchId;
+    }
+
+    public String getAsyncListSeparator()
+    {
+        return asyncListSeparator;
+    }
+
+    public void setAsyncListSeparator(String asyncListSeparator)
+    {
+        this.asyncListSeparator = asyncListSeparator;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean isAsync()
+    {
+        return async;
+    }
+
+    public void setAsync(boolean async)
+    {
+        this.async = async;
+    }
+
+    public String getAsyncId()
+    {
+        return asyncId;
+    }
+
+    public void setAsyncId(String asyncId)
+    {
+        this.asyncId = asyncId;
+    }
+
+    public boolean isWatched()
+    {
+        return watched;
+    }
+
+    public void setWatched(boolean watched)
+    {
+        this.watched = watched;
+    }
+
+    public String getWatchId()
+    {
+        return watchId;
+    }
+
+    public void setWatchId(String watchId)
+    {
+        this.watchId = watchId;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetDataSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetDataSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetDataSpec.java
new file mode 100644
index 0000000..d90a01a
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/GetDataSpec.java
@@ -0,0 +1,89 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class GetDataSpec
+{
+    private String path;
+    private boolean watched;
+    private String watchId;
+    private boolean async;
+    private String asyncId;
+    private boolean decompressed;
+
+    public GetDataSpec()
+    {
+        this("/", false, "", false, "", false);
+    }
+
+    public GetDataSpec(String path, boolean watched, String watchId, boolean async, String asyncId, boolean decompressed)
+    {
+        this.path = path;
+        this.watched = watched;
+        this.watchId = watchId;
+        this.async = async;
+        this.asyncId = asyncId;
+        this.decompressed = decompressed;
+    }
+
+    public String getWatchId()
+    {
+        return watchId;
+    }
+
+    public void setWatchId(String watchId)
+    {
+        this.watchId = watchId;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean isWatched()
+    {
+        return watched;
+    }
+
+    public void setWatched(boolean watched)
+    {
+        this.watched = watched;
+    }
+
+    public boolean isAsync()
+    {
+        return async;
+    }
+
+    public void setAsync(boolean async)
+    {
+        this.async = async;
+    }
+
+    public String getAsyncId()
+    {
+        return asyncId;
+    }
+
+    public void setAsyncId(String asyncId)
+    {
+        this.asyncId = asyncId;
+    }
+
+    public boolean isDecompressed()
+    {
+        return decompressed;
+    }
+
+    public void setDecompressed(boolean decompressed)
+    {
+        this.decompressed = decompressed;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LeaderSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LeaderSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LeaderSpec.java
new file mode 100644
index 0000000..734ee53
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LeaderSpec.java
@@ -0,0 +1,38 @@
+package org.apache.curator.x.rest.entities;
+
+public class LeaderSpec
+{
+    private String path;
+    private String id;
+
+    public LeaderSpec()
+    {
+        this("/", "");
+    }
+
+    public LeaderSpec(String path, String id)
+    {
+        this.path = path;
+        this.id = id;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public void setId(String id)
+    {
+        this.id = id;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LockSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LockSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LockSpec.java
new file mode 100644
index 0000000..76e8e9d
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/LockSpec.java
@@ -0,0 +1,41 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class LockSpec
+{
+    private String path;
+    private int maxWaitMs;
+
+    public LockSpec()
+    {
+        this("", 0);
+    }
+
+    public LockSpec(String path, int maxWaitMs)
+    {
+        this.path = path;
+        this.maxWaitMs = maxWaitMs;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public int getMaxWaitMs()
+    {
+        return maxWaitMs;
+    }
+
+    public void setMaxWaitMs(int maxWaitMs)
+    {
+        this.maxWaitMs = maxWaitMs;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeCacheSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeCacheSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeCacheSpec.java
new file mode 100644
index 0000000..f499bb5
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeCacheSpec.java
@@ -0,0 +1,54 @@
+package org.apache.curator.x.rest.entities;
+
+import org.apache.curator.framework.recipes.cache.PathChildrenCache;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class NodeCacheSpec
+{
+    private String path;
+    private boolean dataIsCompressed;
+    private boolean buildInitial;
+
+    public NodeCacheSpec()
+    {
+        this("/", false, false);
+    }
+
+    public NodeCacheSpec(String path, boolean dataIsCompressed, boolean buildInitial)
+    {
+        this.path = path;
+        this.dataIsCompressed = dataIsCompressed;
+        this.buildInitial = buildInitial;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean isDataIsCompressed()
+    {
+        return dataIsCompressed;
+    }
+
+    public void setDataIsCompressed(boolean dataIsCompressed)
+    {
+        this.dataIsCompressed = dataIsCompressed;
+    }
+
+    public boolean isBuildInitial()
+    {
+        return buildInitial;
+    }
+
+    public void setBuildInitial(boolean buildInitial)
+    {
+        this.buildInitial = buildInitial;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeData.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeData.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeData.java
new file mode 100644
index 0000000..59a3cb8
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/NodeData.java
@@ -0,0 +1,54 @@
+package org.apache.curator.x.rest.entities;
+
+import org.apache.zookeeper.data.Stat;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class NodeData
+{
+    private String path;
+    private Stat stat;
+    private String data;
+
+    public NodeData()
+    {
+        this("/", new Stat(), "");
+    }
+
+    public NodeData(String path, Stat stat, String data)
+    {
+        this.path = path;
+        this.stat = stat;
+        this.data = data;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public Stat getStat()
+    {
+        return stat;
+    }
+
+    public void setStat(Stat stat)
+    {
+        this.stat = stat;
+    }
+
+    public String getData()
+    {
+        return data;
+    }
+
+    public void setData(String data)
+    {
+        this.data = data;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PathChildrenCacheSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PathChildrenCacheSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PathChildrenCacheSpec.java
new file mode 100644
index 0000000..076f2cb
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PathChildrenCacheSpec.java
@@ -0,0 +1,66 @@
+package org.apache.curator.x.rest.entities;
+
+import org.apache.curator.framework.recipes.cache.PathChildrenCache;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class PathChildrenCacheSpec
+{
+    private String path;
+    private boolean cacheData;
+    private boolean dataIsCompressed;
+    private PathChildrenCache.StartMode startMode;
+
+    public PathChildrenCacheSpec()
+    {
+        this("", false, false, PathChildrenCache.StartMode.NORMAL);
+    }
+
+    public PathChildrenCacheSpec(String path, boolean cacheData, boolean dataIsCompressed, PathChildrenCache.StartMode startMode)
+    {
+        this.path = path;
+        this.cacheData = cacheData;
+        this.dataIsCompressed = dataIsCompressed;
+        this.startMode = startMode;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public boolean isCacheData()
+    {
+        return cacheData;
+    }
+
+    public void setCacheData(boolean cacheData)
+    {
+        this.cacheData = cacheData;
+    }
+
+    public boolean isDataIsCompressed()
+    {
+        return dataIsCompressed;
+    }
+
+    public void setDataIsCompressed(boolean dataIsCompressed)
+    {
+        this.dataIsCompressed = dataIsCompressed;
+    }
+
+    public PathChildrenCache.StartMode getStartMode()
+    {
+        return startMode;
+    }
+
+    public void setStartMode(PathChildrenCache.StartMode startMode)
+    {
+        this.startMode = startMode;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PersistentEphemeralNodeSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PersistentEphemeralNodeSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PersistentEphemeralNodeSpec.java
new file mode 100644
index 0000000..1c26df8
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/PersistentEphemeralNodeSpec.java
@@ -0,0 +1,52 @@
+package org.apache.curator.x.rest.entities;
+
+import org.apache.curator.framework.recipes.nodes.PersistentEphemeralNode;
+
+public class PersistentEphemeralNodeSpec
+{
+    private String path;
+    private String data;
+    private PersistentEphemeralNode.Mode mode;
+
+    public PersistentEphemeralNodeSpec()
+    {
+        this("/", "", PersistentEphemeralNode.Mode.EPHEMERAL);
+    }
+
+    public PersistentEphemeralNodeSpec(String path, String data, PersistentEphemeralNode.Mode mode)
+    {
+        this.path = path;
+        this.data = data;
+        this.mode = mode;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public String getData()
+    {
+        return data;
+    }
+
+    public void setData(String data)
+    {
+        this.data = data;
+    }
+
+    public PersistentEphemeralNode.Mode getMode()
+    {
+        return mode;
+    }
+
+    public void setMode(PersistentEphemeralNode.Mode mode)
+    {
+        this.mode = mode;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/2b2e7858/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SemaphoreSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SemaphoreSpec.java b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SemaphoreSpec.java
new file mode 100644
index 0000000..b177f35
--- /dev/null
+++ b/curator-x-rest/src/main/java/org/apache/curator/x/rest/entities/SemaphoreSpec.java
@@ -0,0 +1,65 @@
+package org.apache.curator.x.rest.entities;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+public class SemaphoreSpec
+{
+    private String path;
+    private int acquireQty;
+    private int maxWaitMs;
+    private int maxLeases;
+
+    public SemaphoreSpec()
+    {
+        this("", 0, 0, 0);
+    }
+
+    public SemaphoreSpec(String path, int acquireQty, int maxWaitMs, int maxLeases)
+    {
+        this.path = path;
+        this.acquireQty = acquireQty;
+        this.maxWaitMs = maxWaitMs;
+        this.maxLeases = maxLeases;
+    }
+
+    public int getAcquireQty()
+    {
+        return acquireQty;
+    }
+
+    public void setAcquireQty(int acquireQty)
+    {
+        this.acquireQty = acquireQty;
+    }
+
+    public String getPath()
+    {
+        return path;
+    }
+
+    public void setPath(String path)
+    {
+        this.path = path;
+    }
+
+    public int getMaxWaitMs()
+    {
+        return maxWaitMs;
+    }
+
+    public void setMaxWaitMs(int maxWaitMs)
+    {
+        this.maxWaitMs = maxWaitMs;
+    }
+
+    public int getMaxLeases()
+    {
+        return maxLeases;
+    }
+
+    public void setMaxLeases(int maxLeases)
+    {
+        this.maxLeases = maxLeases;
+    }
+}