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 2017/07/08 03:32:37 UTC

[5/6] curator git commit: Squashed commit of the following:

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/models/Priority.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/models/Priority.java b/curator-examples/src/main/java/pubsub/models/Priority.java
new file mode 100644
index 0000000..3b10f75
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/models/Priority.java
@@ -0,0 +1,26 @@
+/**
+ * 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.
+ */
+package pubsub.models;
+
+public enum Priority
+{
+    low,
+    medium,
+    high
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/resources/log4j.properties b/curator-examples/src/main/resources/log4j.properties
new file mode 100644
index 0000000..0405670
--- /dev/null
+++ b/curator-examples/src/main/resources/log4j.properties
@@ -0,0 +1,24 @@
+#
+# 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.
+#
+
+log4j.rootLogger=ERROR, console
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%-5p %c %x %m [%t]%n

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/site/confluence/index.confluence
----------------------------------------------------------------------
diff --git a/curator-examples/src/site/confluence/index.confluence b/curator-examples/src/site/confluence/index.confluence
index 928b44f..f9be506 100644
--- a/curator-examples/src/site/confluence/index.confluence
+++ b/curator-examples/src/site/confluence/index.confluence
@@ -7,6 +7,7 @@ This module contains example usages of various Curator features. Each directory
 |/locking|Example of using InterProcessMutex|
 |/discovery|Example usage of the Curator's ServiceDiscovery|
 |/framework|A few examples of how to use the CuratorFramework class|
+|/async|Example AsyncCuratorFramework code|
+|/modeled|ModeledFramework and Modeled Cache examples|
 
-See the [examples source repo|https://git-wip-us.apache.org/repos/asf?p=curator.git;a=tree;f=curator-examples/src/main/java] for each example.
-
+See the [examples source repo|https://github.com/apache/curator/tree/master/curator-examples/src/main/java] for each example.

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-framework/src/main/java/org/apache/curator/framework/api/CreateBuilder.java
----------------------------------------------------------------------
diff --git a/curator-framework/src/main/java/org/apache/curator/framework/api/CreateBuilder.java b/curator-framework/src/main/java/org/apache/curator/framework/api/CreateBuilder.java
index 59b3510..b310f06 100644
--- a/curator-framework/src/main/java/org/apache/curator/framework/api/CreateBuilder.java
+++ b/curator-framework/src/main/java/org/apache/curator/framework/api/CreateBuilder.java
@@ -32,5 +32,15 @@ public interface CreateBuilder extends CreateBuilderMain
      */
     CreateBuilderMain withTtl(long ttl);
 
+    /**
+     * If the ZNode already exists, Curator will instead call setData()
+     */
     CreateBuilder2 orSetData();
+
+    /**
+     * If the ZNode already exists, Curator will instead call setData()
+     *
+     * @param version the version to use for {@link org.apache.curator.framework.CuratorFramework#setData()}
+     */
+    CreateBuilder2 orSetData(int version);
 }

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-framework/src/main/java/org/apache/curator/framework/imps/CreateBuilderImpl.java
----------------------------------------------------------------------
diff --git a/curator-framework/src/main/java/org/apache/curator/framework/imps/CreateBuilderImpl.java b/curator-framework/src/main/java/org/apache/curator/framework/imps/CreateBuilderImpl.java
index fdd1e15..406d972 100644
--- a/curator-framework/src/main/java/org/apache/curator/framework/imps/CreateBuilderImpl.java
+++ b/curator-framework/src/main/java/org/apache/curator/framework/imps/CreateBuilderImpl.java
@@ -52,6 +52,7 @@ public class CreateBuilderImpl implements CreateBuilder, CreateBuilder2, Backgro
     private boolean doProtected;
     private boolean compress;
     private boolean setDataIfExists;
+    private int setDataIfExistsVersion = -1;
     private String protectedId;
     private ACLing acling;
     private Stat storingStat;
@@ -95,10 +96,22 @@ public class CreateBuilderImpl implements CreateBuilder, CreateBuilder2, Backgro
         this.ttl = ttl;
     }
 
+    public void setSetDataIfExistsVersion(int version)
+    {
+        this.setDataIfExistsVersion = version;
+    }
+
     @Override
     public CreateBuilder2 orSetData()
     {
+        return orSetData(-1);
+    }
+
+    @Override
+    public CreateBuilder2 orSetData(int version)
+    {
         setDataIfExists = true;
+        setDataIfExistsVersion = version;
         return this;
     }
 
@@ -751,7 +764,7 @@ public class CreateBuilderImpl implements CreateBuilder, CreateBuilder2, Backgro
             {
                 try
                 {
-                    client.getZooKeeper().setData(path, mainOperationAndData.getData().getData(), -1, statCallback, backgrounding.getContext());
+                    client.getZooKeeper().setData(path, mainOperationAndData.getData().getData(), setDataIfExistsVersion, statCallback, backgrounding.getContext());
                 }
                 catch ( KeeperException e )
                 {
@@ -1078,7 +1091,7 @@ public class CreateBuilderImpl implements CreateBuilder, CreateBuilder2, Backgro
                             {
                                 if ( setDataIfExists )
                                 {
-                                    client.getZooKeeper().setData(path, data, -1);
+                                    client.getZooKeeper().setData(path, data, setDataIfExistsVersion);
                                     createdPath = path;
                                 }
                                 else

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-framework/src/main/java/org/apache/curator/framework/schema/Schema.java
----------------------------------------------------------------------
diff --git a/curator-framework/src/main/java/org/apache/curator/framework/schema/Schema.java b/curator-framework/src/main/java/org/apache/curator/framework/schema/Schema.java
index e9f4f18..bcb35d3 100644
--- a/curator-framework/src/main/java/org/apache/curator/framework/schema/Schema.java
+++ b/curator-framework/src/main/java/org/apache/curator/framework/schema/Schema.java
@@ -318,7 +318,7 @@ public class Schema
             ", pathRegex=" + pathRegex +
             ", path='" + fixedPath + '\'' +
             ", documentation='" + documentation + '\'' +
-            ", dataValidator=" + schemaValidator +
+            ", dataValidator=" + schemaValidator.getClass() +
             ", ephemeral=" + ephemeral +
             ", sequential=" + sequential +
             ", watched=" + watched +

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/NodeCache.java
----------------------------------------------------------------------
diff --git a/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/NodeCache.java b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/NodeCache.java
index 9a6eaa7..9687e1b 100644
--- a/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/NodeCache.java
+++ b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/NodeCache.java
@@ -145,6 +145,11 @@ public class NodeCache implements Closeable
         this.dataIsCompressed = dataIsCompressed;
     }
 
+    public CuratorFramework getClient()
+    {
+        return client;
+    }
+
     /**
      * Start the cache. The cache is not started automatically. You must call this method.
      *
@@ -233,6 +238,16 @@ public class NodeCache implements Closeable
         return data.get();
     }
 
+    /**
+     * Return the path this cache is watching
+     *
+     * @return path
+     */
+    public String getPath()
+    {
+        return path;
+    }
+
     @VisibleForTesting
     volatile Exchanger<Object> rebuildTestExchanger;
 

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/PathChildrenCache.java
----------------------------------------------------------------------
diff --git a/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/PathChildrenCache.java b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/PathChildrenCache.java
index d11ced6..c5449f2 100644
--- a/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/PathChildrenCache.java
+++ b/curator-recipes/src/main/java/org/apache/curator/framework/recipes/cache/PathChildrenCache.java
@@ -133,7 +133,7 @@ public class PathChildrenCache implements Closeable
             handleStateChange(newState);
         }
     };
-    private static final ThreadFactory defaultThreadFactory = ThreadUtils.newThreadFactory("PathChildrenCache");
+    public static final ThreadFactory defaultThreadFactory = ThreadUtils.newThreadFactory("PathChildrenCache");
 
     /**
      * @param client the client

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-test/src/main/java/org/apache/curator/test/BaseClassForTests.java
----------------------------------------------------------------------
diff --git a/curator-test/src/main/java/org/apache/curator/test/BaseClassForTests.java b/curator-test/src/main/java/org/apache/curator/test/BaseClassForTests.java
index 5114552..8238c82 100644
--- a/curator-test/src/main/java/org/apache/curator/test/BaseClassForTests.java
+++ b/curator-test/src/main/java/org/apache/curator/test/BaseClassForTests.java
@@ -27,12 +27,12 @@ import org.testng.IRetryAnalyzer;
 import org.testng.ITestContext;
 import org.testng.ITestNGMethod;
 import org.testng.ITestResult;
-import org.testng.TestListenerAdapter;
 import org.testng.annotations.AfterMethod;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.BeforeSuite;
 import java.io.IOException;
 import java.net.BindException;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -155,7 +155,7 @@ public class BaseClassForTests
         RetryAnalyzer(Logger log, RetryContext retryContext)
         {
             this.log = log;
-            this.retryContext = retryContext;
+            this.retryContext = Objects.requireNonNull(retryContext, "retryContext cannot be null");
         }
 
         @Override
@@ -218,7 +218,11 @@ public class BaseClassForTests
             }
             else if ( method.isTestMethod() )
             {
-                method.getTestMethod().setRetryAnalyzer(new RetryAnalyzer(log, (RetryContext)context.getAttribute(ATTRIBUTE_NAME)));
+                RetryContext retryContext = (RetryContext)context.getAttribute(ATTRIBUTE_NAME);
+                if ( retryContext != null )
+                {
+                    method.getTestMethod().setRetryAnalyzer(new RetryAnalyzer(log, retryContext));
+                }
             }
         }
 

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/pom.xml
----------------------------------------------------------------------
diff --git a/curator-x-async/pom.xml b/curator-x-async/pom.xml
index a7fdcd5..925896b 100644
--- a/curator-x-async/pom.xml
+++ b/curator-x-async/pom.xml
@@ -21,6 +21,18 @@
 
         <dependency>
             <groupId>org.apache.curator</groupId>
+            <artifactId>curator-recipes</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.curator</groupId>
             <artifactId>curator-test</artifactId>
             <scope>test</scope>
         </dependency>
@@ -30,6 +42,12 @@
             <artifactId>testng</artifactId>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncStage.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncStage.java b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncStage.java
index 30ed234..ad7547b 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncStage.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncStage.java
@@ -27,9 +27,18 @@ import java.util.concurrent.CompletionStage;
 public interface AsyncStage<T> extends CompletionStage<T>
 {
     /**
-     * If the {@link org.apache.curator.x.async.api.WatchableAsyncCuratorFramework} facade is
-     * used (via {@link AsyncCuratorFramework#watched()}), this returns the completion
-     * stage used when the watcher is triggered
+     * <p>
+     *     If the {@link org.apache.curator.x.async.api.WatchableAsyncCuratorFramework} facade is
+     *     used (via {@link AsyncCuratorFramework#watched()}), this returns the completion
+     *     stage used when the watcher is triggered
+     * </p>
+     *
+     * <p>
+     *     Also, applies to {@link org.apache.curator.x.async.modeled.ModeledFramework}
+     *     when {@link org.apache.curator.x.async.modeled.ModeledFrameworkBuilder#watched(WatchMode)}
+     *     or {@link org.apache.curator.x.async.modeled.ModeledFrameworkBuilder#watched(WatchMode, java.util.function.UnaryOperator)}
+     *     is used.
+     * </p>
      *
      * @return CompletionStage for the set watcher or <code>null</code>
      */

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java
new file mode 100644
index 0000000..e982cf2
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java
@@ -0,0 +1,297 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async;
+
+import org.apache.curator.framework.recipes.locks.InterProcessLock;
+import org.apache.curator.utils.ThreadUtils;
+import org.apache.curator.x.async.modeled.ZPath;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ *     Utility for adding asynchronous behavior
+ * </p>
+ *
+ * <p>
+ *     E.g. locks:
+ * <code><pre>
+ *     InterProcessMutex mutex = new InterProcessMutex(...) // or any InterProcessLock
+ *     AsyncWrappers.lockAsync(mutex, executor).thenAccept(dummy -> {
+ *         try
+ *         {
+ *             // do work while holding the lock
+ *         }
+ *         finally
+ *         {
+ *             AsyncWrappers.release(mutex);
+ *         }
+ *     }).exceptionally(e -> {
+ *         if ( e instanceOf TimeoutException ) {
+ *             // timed out trying to acquire the lock
+ *         }
+ *         // handle the error
+ *         return null;
+ *     });
+ * </pre></code>
+ * </p>
+ *
+ * <p>
+ *     E.g. EnsureContainers
+ * <code><pre>
+ *     AsyncWrappers.(client, path, executor).thenAccept(dummy -> {
+ *         // execute after ensuring containers
+ *     });
+ * </pre></code>
+ * </p>
+ */
+public class AsyncWrappers
+{
+    /**
+     * Asynchronously call {@link org.apache.curator.framework.CuratorFramework#createContainers(String)} using
+     * the {@link java.util.concurrent.ForkJoinPool#commonPool()}.
+     *
+     * @param client client
+     * @param path path to ensure
+     * @return stage
+     */
+    public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path)
+    {
+        return asyncEnsureContainers(client, path, null);
+    }
+
+    /**
+     * Asynchronously call {@link org.apache.curator.framework.CuratorFramework#createContainers(String)} using
+     * the given executor
+     *
+     * @param client client
+     * @param path path to ensure
+     * @return stage
+     */
+    public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path, Executor executor)
+    {
+        Runnable proc = () -> {
+            try
+            {
+                client.unwrap().createContainers(path.fullPath());
+            }
+            catch ( Exception e )
+            {
+                throw new RuntimeException(e);
+            }
+        };
+        return (executor != null) ? CompletableFuture.runAsync(proc, executor) : CompletableFuture.runAsync(proc);
+    }
+
+    /**
+     * Set as the completion stage's exception when trying to acquire a lock
+     * times out
+     */
+    public static class TimeoutException extends RuntimeException
+    {
+    }
+
+    /**
+     * Attempt to acquire the given lock asynchronously using the given timeout and executor. If the lock
+     * is not acquired within the timeout stage is completedExceptionally with {@link AsyncWrappers.TimeoutException}
+     *
+     * @param lock a lock implementation (e.g. {@link org.apache.curator.framework.recipes.locks.InterProcessMutex},
+     * {@link org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2}, etc.)
+     * @param timeout max timeout to acquire lock
+     * @param unit time unit of timeout
+     * @param executor executor to use to asynchronously acquire
+     * @return stage
+     */
+    public static CompletionStage<Void> lockAsync(InterProcessLock lock, long timeout, TimeUnit unit, Executor executor)
+    {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        if ( executor == null )
+        {
+            CompletableFuture.runAsync(() -> lock(future, lock, timeout, unit));
+        }
+        else
+        {
+            CompletableFuture.runAsync(() -> lock(future, lock, timeout, unit), executor);
+        }
+        return future;
+    }
+
+    /**
+     * Attempt to acquire the given lock asynchronously using the given timeout and executor. The stage
+     * is completed with a Boolean that indicates whether or not the lock was acquired.
+     *
+     * @param lock a lock implementation (e.g. {@link org.apache.curator.framework.recipes.locks.InterProcessMutex},
+     * {@link org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2}, etc.)
+     * @param timeout max timeout to acquire lock
+     * @param unit time unit of timeout
+     * @param executor executor to use to asynchronously acquire
+     * @return stage
+     */
+    public static CompletionStage<Boolean> lockAsyncIf(InterProcessLock lock, long timeout, TimeUnit unit, Executor executor)
+    {
+        CompletableFuture<Boolean> future = new CompletableFuture<>();
+        if ( executor == null )
+        {
+            CompletableFuture.runAsync(() -> lockIf(future, lock, timeout, unit));
+        }
+        else
+        {
+            CompletableFuture.runAsync(() -> lockIf(future, lock, timeout, unit), executor);
+        }
+        return future;
+    }
+
+    /**
+     * Attempt to acquire the given lock asynchronously using the given executor and without a timeout.
+     *
+     * @param lock a lock implementation (e.g. {@link org.apache.curator.framework.recipes.locks.InterProcessMutex},
+     * {@link org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2}, etc.)
+     * @param executor executor to use to asynchronously acquire
+     * @return stage
+     */
+    public static CompletionStage<Void> lockAsync(InterProcessLock lock, Executor executor)
+    {
+        return lockAsync(lock, 0, null, executor);
+    }
+
+    /**
+     * Attempt to acquire the given lock asynchronously using the given timeout using the {@link java.util.concurrent.ForkJoinPool#commonPool()}.
+     * If the lock is not acquired within the timeout stage is completedExceptionally with {@link AsyncWrappers.TimeoutException}
+     *
+     * @param lock a lock implementation (e.g. {@link org.apache.curator.framework.recipes.locks.InterProcessMutex},
+     * {@link org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2}, etc.)
+     * @param timeout max timeout to acquire lock
+     * @param unit time unit of timeout
+     * @return stage
+     */
+    public static CompletionStage<Void> lockAsync(InterProcessLock lock, long timeout, TimeUnit unit)
+    {
+        return lockAsync(lock, timeout, unit, null);
+    }
+
+    /**
+     * Attempt to acquire the given lock asynchronously using the given timeout using the {@link java.util.concurrent.ForkJoinPool#commonPool()}.
+     * The stage is completed with a Boolean that indicates whether or not the lock was acquired.
+     *
+     * @param lock a lock implementation (e.g. {@link org.apache.curator.framework.recipes.locks.InterProcessMutex},
+     * {@link org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2}, etc.)
+     * @param timeout max timeout to acquire lock
+     * @param unit time unit of timeout
+     * @return stage
+     */
+    public static CompletionStage<Boolean> lockAsyncIf(InterProcessLock lock, long timeout, TimeUnit unit)
+    {
+        return lockAsyncIf(lock, timeout, unit, null);
+    }
+
+    /**
+     * Attempt to acquire the given lock asynchronously without timeout using the {@link java.util.concurrent.ForkJoinPool#commonPool()}.
+     *
+     * @param lock a lock implementation (e.g. {@link org.apache.curator.framework.recipes.locks.InterProcessMutex},
+     * {@link org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2}, etc.)
+     * @return stage
+     */
+    public static CompletionStage<Void> lockAsync(InterProcessLock lock)
+    {
+        return lockAsync(lock, 0, null, null);
+    }
+
+    /**
+     * Release the lock and wrap any exception in <code>RuntimeException</code>
+     *
+     * @param lock lock to release
+     */
+    public static void release(InterProcessLock lock)
+    {
+        release(lock, true);
+    }
+
+    /**
+     * Release the lock and wrap any exception in <code>RuntimeException</code>
+     *
+     * @param lock lock to release
+     * @param ignoreNoLockExceptions if true {@link java.lang.IllegalStateException} is ignored
+     */
+    public static void release(InterProcessLock lock, boolean ignoreNoLockExceptions)
+    {
+        try
+        {
+            lock.release();
+        }
+        catch ( IllegalStateException e )
+        {
+            if ( !ignoreNoLockExceptions )
+            {
+                throw new RuntimeException(e);
+            }
+        }
+        catch ( Exception e )
+        {
+            ThreadUtils.checkInterrupted(e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void lockIf(CompletableFuture<Boolean> future, InterProcessLock lock, long timeout, TimeUnit unit)
+    {
+        try
+        {
+            future.complete(lock.acquire(timeout, unit));
+        }
+        catch ( Exception e )
+        {
+            ThreadUtils.checkInterrupted(e);
+            future.completeExceptionally(e);
+        }
+    }
+
+    private static void lock(CompletableFuture<Void> future, InterProcessLock lock, long timeout, TimeUnit unit)
+    {
+        try
+        {
+            if ( unit != null )
+            {
+                if ( lock.acquire(timeout, unit) )
+                {
+                    future.complete(null);
+                }
+                else
+                {
+                    future.completeExceptionally(new TimeoutException());
+                }
+            }
+            else
+            {
+                lock.acquire();
+                future.complete(null);
+            }
+        }
+        catch ( Exception e )
+        {
+            ThreadUtils.checkInterrupted(e);
+            future.completeExceptionally(e);
+        }
+    }
+
+    private AsyncWrappers()
+    {
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCreateBuilder.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCreateBuilder.java b/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCreateBuilder.java
index e5f2d8c..7ed934e 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCreateBuilder.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCreateBuilder.java
@@ -67,6 +67,15 @@ public interface AsyncCreateBuilder extends AsyncPathAndBytesable<AsyncStage<Str
     AsyncPathAndBytesable<AsyncStage<String>> withTtl(long ttl);
 
     /**
+     * Specify the setData expected matching version when using option
+     * {@link org.apache.curator.x.async.api.CreateOption#setDataIfExists}. By default -1 is used.
+     *
+     * @param version setData expected matching version
+     * @return this for chaining
+     */
+    AsyncPathAndBytesable<AsyncStage<String>> withSetDataVersion(int version);
+
+    /**
      * Options to change how the ZNode is created
      *
      * @param options options
@@ -141,4 +150,23 @@ public interface AsyncCreateBuilder extends AsyncPathAndBytesable<AsyncStage<Str
      * @return this
      */
     AsyncPathAndBytesable<AsyncStage<String>> withOptions(Set<CreateOption> options, CreateMode createMode, List<ACL> aclList, Stat stat, long ttl);
+
+    /**
+     * set options, mode, ACLs, and stat
+     *
+     * @param options options
+     * @param createMode mode to use
+     * @param aclList the ACL list to use
+     * @param stat the stat to have filled in
+     * @param ttl the ttl or 0
+     * @param setDataVersion the setData matching version or -1
+     * @see #withOptions(java.util.Set)
+     * @see #withMode(org.apache.zookeeper.CreateMode)
+     * @see #withACL(java.util.List)
+     * @see #storingStatIn(org.apache.zookeeper.data.Stat)
+     * @see #withTtl(long)
+     * @see #withSetDataVersion(long)
+     * @return this
+     */
+    AsyncPathAndBytesable<AsyncStage<String>> withOptions(Set<CreateOption> options, CreateMode createMode, List<ACL> aclList, Stat stat, long ttl, int setDataVersion);
 }

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCuratorFrameworkDsl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCuratorFrameworkDsl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCuratorFrameworkDsl.java
index 0807160..bc66bb6 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCuratorFrameworkDsl.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/api/AsyncCuratorFrameworkDsl.java
@@ -19,7 +19,6 @@
 package org.apache.curator.x.async.api;
 
 import org.apache.curator.framework.api.transaction.CuratorOp;
-import org.apache.curator.x.async.WatchMode;
 
 /**
  * Zookeeper framework-style client

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/details/AsyncCreateBuilderImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/details/AsyncCreateBuilderImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/details/AsyncCreateBuilderImpl.java
index e8b1d30..c27639e 100644
--- a/curator-x-async/src/main/java/org/apache/curator/x/async/details/AsyncCreateBuilderImpl.java
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/details/AsyncCreateBuilderImpl.java
@@ -44,6 +44,7 @@ class AsyncCreateBuilderImpl implements AsyncCreateBuilder
     private Set<CreateOption> options = Collections.emptySet();
     private Stat stat = null;
     private long ttl = -1;
+    private int setDataVersion = -1;
 
     AsyncCreateBuilderImpl(CuratorFrameworkImpl client, Filters filters)
     {
@@ -80,6 +81,13 @@ class AsyncCreateBuilderImpl implements AsyncCreateBuilder
     }
 
     @Override
+    public AsyncPathAndBytesable<AsyncStage<String>> withSetDataVersion(int version)
+    {
+        this.setDataVersion = version;
+        return this;
+    }
+
+    @Override
     public AsyncPathAndBytesable<AsyncStage<String>> withOptions(Set<CreateOption> options)
     {
         this.options = Objects.requireNonNull(options, "options cannot be null");
@@ -133,6 +141,18 @@ class AsyncCreateBuilderImpl implements AsyncCreateBuilder
     }
 
     @Override
+    public AsyncPathAndBytesable<AsyncStage<String>> withOptions(Set<CreateOption> options, CreateMode createMode, List<ACL> aclList, Stat stat, long ttl, int setDataVersion)
+    {
+        this.options = Objects.requireNonNull(options, "options cannot be null");
+        this.aclList = aclList;
+        this.createMode = Objects.requireNonNull(createMode, "createMode cannot be null");
+        this.stat = stat;
+        this.ttl = ttl;
+        this.setDataVersion = setDataVersion;
+        return this;
+    }
+
+    @Override
     public AsyncStage<String> forPath(String path)
     {
         return internalForPath(path, null, false);
@@ -159,6 +179,7 @@ class AsyncCreateBuilderImpl implements AsyncCreateBuilder
             stat,
             ttl
         );
+        builder.setSetDataIfExistsVersion(setDataVersion);
         return safeCall(common.internalCallback, () -> useData ? builder.forPath(path, data) : builder.forPath(path));
     }
 }

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/JacksonModelSerializer.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/JacksonModelSerializer.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/JacksonModelSerializer.java
new file mode 100644
index 0000000..b4e5601
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/JacksonModelSerializer.java
@@ -0,0 +1,124 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Model serializer that uses Jackson for JSON serialization. <strong>IMPORTANT: </strong>
+ * the jackson dependency is specified as <code>provided</code> in the curator-x-async Maven POM
+ * file to avoid adding a new dependency to Curator. Therefore, if you wish to use the
+ * JacksonModelSerializer you must manually add the dependency to your build system
+ */
+public class JacksonModelSerializer<T> implements ModelSerializer<T>
+{
+    private static final ObjectMapper mapper = new ObjectMapper();
+    static
+    {
+        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+    }
+
+    private final ObjectReader reader;
+    private final ObjectWriter writer;
+
+    public static <T> JacksonModelSerializer<T> build(Class<T> modelClass)
+    {
+        return new JacksonModelSerializer<>(modelClass);
+    }
+
+    public static <T> JacksonModelSerializer<T> build(JavaType type)
+    {
+        return new JacksonModelSerializer<>(type);
+    }
+
+    public static <T> JacksonModelSerializer<T> build(TypeReference type)
+    {
+        return new JacksonModelSerializer<>(type);
+    }
+
+    public JacksonModelSerializer(Class<T> modelClass)
+    {
+        this(mapper.getTypeFactory().constructType(modelClass));
+    }
+
+    public JacksonModelSerializer(JavaType type)
+    {
+        reader = mapper.readerFor(type);
+        writer = mapper.writerFor(type);
+    }
+
+    public JacksonModelSerializer(TypeReference type)
+    {
+        reader = mapper.readerFor(type);
+        writer = mapper.writerFor(type);
+    }
+
+    public JacksonModelSerializer(ObjectMapper mapper, JavaType type)
+    {
+        reader = mapper.readerFor(type);
+        writer = mapper.writerFor(type);
+    }
+
+    public JacksonModelSerializer(ObjectMapper mapper, TypeReference type)
+    {
+        reader = mapper.readerFor(type);
+        writer = mapper.writerFor(type);
+    }
+
+    public JacksonModelSerializer(ObjectReader reader, ObjectWriter writer)
+    {
+        this.reader = Objects.requireNonNull(reader, "reader cannot be null");
+        this.writer = Objects.requireNonNull(writer, "writer cannot be null");
+    }
+
+    @Override
+    public byte[] serialize(T model)
+    {
+        try
+        {
+            return writer.writeValueAsBytes(model);
+        }
+        catch ( JsonProcessingException e )
+        {
+            throw new RuntimeException(String.format("Could not serialize value: %s", model), e);
+        }
+    }
+
+    @Override
+    public T deserialize(byte[] bytes)
+    {
+        try
+        {
+            return reader.readValue(bytes);
+        }
+        catch ( IOException e )
+        {
+            throw new RuntimeException(String.format("Could not deserialize value: %s", Arrays.toString(bytes)), e);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java
new file mode 100644
index 0000000..428096e
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java
@@ -0,0 +1,43 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+/**
+ * Serializing interface for models
+ */
+public interface ModelSerializer<T>
+{
+    /**
+     * Given a model return the serialized bytes
+     *
+     * @param model model
+     * @return bytes
+     */
+    byte[] serialize(T model);
+
+    /**
+     * Given bytes serialized via {@link #serialize(Object)} return
+     * the model
+     *
+     * @param bytes serialized bytes
+     * @return model
+     * @throws RuntimeException if <code>bytes</code> is invalid or there was an error deserializing
+     */
+    T deserialize(byte[] bytes);
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpec.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpec.java
new file mode 100644
index 0000000..2fe5242
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpec.java
@@ -0,0 +1,217 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.curator.framework.schema.Schema;
+import org.apache.curator.x.async.api.CreateOption;
+import org.apache.curator.x.async.api.DeleteOption;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.data.ACL;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A full specification for dealing with a portion of the ZooKeeper tree. ModelSpec's contain:
+ *
+ * <ul>
+ *     <li>A node path</li>
+ *     <li>Serializer for the data stored</li>
+ *     <li>Options for how to create the node (mode, compression, etc.)</li>
+ *     <li>Options for how to deleting the node (quietly, guaranteed, etc.)</li>
+ *     <li>ACLs</li>
+ *     <li>Optional schema generation</li>
+ * </ul>
+ */
+public interface ModelSpec<T> extends Resolvable
+{
+    Set<CreateOption> defaultCreateOptions = ImmutableSet.of(CreateOption.createParentsAsContainers, CreateOption.setDataIfExists);
+    Set<DeleteOption> defaultDeleteOptions = ImmutableSet.of(DeleteOption.guaranteed);
+
+    /**
+     * Start a new ModelSpecBuilder for the given path and serializer. The returned ModelSpecBuilder
+     * uses {@link #defaultCreateOptions} and {@link #defaultDeleteOptions}, but you can change these
+     * with builder methods.
+     *
+     * @param path path to model
+     * @param serializer the model's serializer
+     * @return builder
+     */
+    static <T> ModelSpecBuilder<T> builder(ZPath path, ModelSerializer<T> serializer)
+    {
+        return new ModelSpecBuilder<>(path, serializer)
+            .withCreateOptions(defaultCreateOptions)
+            .withDeleteOptions(defaultDeleteOptions);
+    }
+
+    /**
+     * Start a new ModelSpecBuilder for the given serializer. The returned ModelSpecBuilder
+     * uses {@link #defaultCreateOptions} and {@link #defaultDeleteOptions}, but you can change these
+     * with builder methods. You must set a path before calling {@link ModelSpecBuilder#build()}
+     *
+     * @param serializer the model's serializer
+     * @return builder
+     */
+    static <T> ModelSpecBuilder<T> builder(ModelSerializer<T> serializer)
+    {
+        return new ModelSpecBuilder<>(serializer)
+            .withCreateOptions(defaultCreateOptions)
+            .withDeleteOptions(defaultDeleteOptions);
+    }
+
+    /**
+     * <p>
+     *     Return a new CuratorModel instance with all the same options but applying to the given child node of this CuratorModel's
+     *     path. E.g. if this CuratorModel instance applies to "/a/b", calling <code>modeled.at("c")</code> returns an instance that applies to
+     *     "/a/b/c".
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of child or,
+     *     if it implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @param child child node.
+     * @return new Modeled Spec instance
+     */
+    ModelSpec<T> child(Object child);
+
+    /**
+     * <p>
+     *     Return a new CuratorModel instance with all the same options but applying to the parent node of this CuratorModel's
+     *     path. E.g. if this CuratorModel instance applies to "/a/b/c", calling <code>modeled.parent()</code> returns an instance that applies to
+     *     "/a/b".
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of child or,
+     *     if it implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @return new Modeled Spec instance
+     */
+    ModelSpec<T> parent();
+
+    /**
+     * Return a new CuratorModel instance with all the same options but using the given path.
+     *
+     * @param path new path
+     * @return new Modeled Spec instance
+     */
+    ModelSpec<T> withPath(ZPath path);
+
+    /**
+     * <p>
+     *     Return a new CuratorModel instance with all the same options but using a resolved
+     *     path by calling {@link org.apache.curator.x.async.modeled.ZPath#resolved(Object...)}
+     *     using the given parameters
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of the parameter object or,
+     *     if the object implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @param parameters list of replacements. Must have be the same length as the number of
+     *                   parameter nodes in the path
+     * @return new resolved ModelSpec
+     */
+    @Override
+    ModelSpec<T> resolved(Object... parameters);
+
+    /**
+     * <p>
+     *     Return a new CuratorModel instance with all the same options but using a resolved
+     *     path by calling {@link org.apache.curator.x.async.modeled.ZPath#resolved(java.util.List)}
+     *     using the given parameters
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of the parameter object or,
+     *     if the object implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @param parameters list of replacements. Must have be the same length as the number of
+     *                   parameter nodes in the path
+     * @return new resolved ModelSpec
+     */
+    @Override
+    ModelSpec<T> resolved(List<Object> parameters);
+
+    /**
+     * Return the model's path
+     *
+     * @return path
+     */
+    ZPath path();
+
+    /**
+     * Return the model's serializer
+     *
+     * @return serializer
+     */
+    ModelSerializer<T> serializer();
+
+    /**
+     * Return the model's create mode
+     *
+     * @return create mode
+     */
+    CreateMode createMode();
+
+    /**
+     * Return the model's ACL list
+     *
+     * @return ACL list
+     */
+    List<ACL> aclList();
+
+    /**
+     * Return the model's create options
+     *
+     * @return create options
+     */
+    Set<CreateOption> createOptions();
+
+    /**
+     * Return the model's delete options
+     *
+     * @return delete options
+     */
+    Set<DeleteOption> deleteOptions();
+
+    /**
+     * Return the TTL to use or -1
+     *
+     * @return ttl
+     */
+    long ttl();
+
+    /**
+     * Return a Curator schema that validates ZNodes at this model's
+     * path using this model's values
+     *
+     * @return schema
+     */
+    Schema schema();
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpecBuilder.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpecBuilder.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpecBuilder.java
new file mode 100644
index 0000000..f6a2a51
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSpecBuilder.java
@@ -0,0 +1,138 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import com.google.common.collect.ImmutableSet;
+import org.apache.curator.x.async.api.CreateOption;
+import org.apache.curator.x.async.api.DeleteOption;
+import org.apache.curator.x.async.modeled.details.ModelSpecImpl;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.data.ACL;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+public class ModelSpecBuilder<T>
+{
+    private final ModelSerializer<T> serializer;
+    private ZPath path;
+    private CreateMode createMode = CreateMode.PERSISTENT;
+    private List<ACL> aclList = Collections.emptyList();
+    private Set<CreateOption> createOptions = Collections.emptySet();
+    private Set<DeleteOption> deleteOptions = Collections.emptySet();
+    private long ttl = -1;
+
+    /**
+     * Build a new ModelSpec instance
+     *
+     * @return new ModelSpec instance
+     */
+    public ModelSpec<T> build()
+    {
+        return new ModelSpecImpl<>(path, serializer, createMode, aclList, createOptions, deleteOptions, ttl);
+    }
+
+    /**
+     * Use the given createMode for create operations on the Modeled Curator's ZNode
+     *
+     * @param createMode create mode
+     * @return this for chaining
+     */
+    public ModelSpecBuilder<T> withCreateMode(CreateMode createMode)
+    {
+        this.createMode = createMode;
+        return this;
+    }
+
+    /**
+     * Specify a TTL when mode is {@link org.apache.zookeeper.CreateMode#PERSISTENT_WITH_TTL} or
+     * {@link org.apache.zookeeper.CreateMode#PERSISTENT_SEQUENTIAL_WITH_TTL}. If
+     * the znode has not been modified within the given TTL, it will be deleted once it has no
+     * children. The TTL unit is milliseconds and must be greater than 0 and less than or equal to
+     * EphemeralType.MAX_TTL.
+     *
+     * @param ttl the ttl
+     * @return this for chaining
+     */
+    public ModelSpecBuilder<T> withTtl(long ttl)
+    {
+        this.ttl = ttl;
+        return this;
+    }
+
+    /**
+     * Use the given aclList for create operations on the Modeled Curator's ZNode
+     *
+     * @param aclList ACLs
+     * @return this for chaining
+     */
+    public ModelSpecBuilder<T> withAclList(List<ACL> aclList)
+    {
+        this.aclList = aclList;
+        return this;
+    }
+
+    /**
+     * Use the given create options on the Modeled Curator's ZNode
+     *
+     * @param createOptions options
+     * @return this for chaining
+     */
+    public ModelSpecBuilder<T> withCreateOptions(Set<CreateOption> createOptions)
+    {
+        this.createOptions = (createOptions != null) ? ImmutableSet.copyOf(createOptions) : null;
+        return this;
+    }
+
+    /**
+     * Use the given delete options on the Modeled Curator's ZNode
+     *
+     * @param deleteOptions options
+     * @return this for chaining
+     */
+    public ModelSpecBuilder<T> withDeleteOptions(Set<DeleteOption> deleteOptions)
+    {
+        this.deleteOptions = (deleteOptions != null) ? ImmutableSet.copyOf(deleteOptions) : null;
+        return this;
+    }
+
+    /**
+     * Change the model spec's path
+     *
+     * @param path new path
+     * @return this for chaining
+     */
+    public ModelSpecBuilder<T> withPath(ZPath path)
+    {
+        this.path = Objects.requireNonNull(path, "path cannot be null");
+        return this;
+    }
+
+    ModelSpecBuilder(ModelSerializer<T> serializer)
+    {
+        this.serializer = Objects.requireNonNull(serializer, "serializer cannot be null");
+    }
+
+    ModelSpecBuilder(ZPath path, ModelSerializer<T> serializer)
+    {
+        this.path = Objects.requireNonNull(path, "path cannot be null");
+        this.serializer = Objects.requireNonNull(serializer, "serializer cannot be null");
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFramework.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFramework.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFramework.java
new file mode 100644
index 0000000..8f03387
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFramework.java
@@ -0,0 +1,371 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import org.apache.curator.framework.api.transaction.CuratorOp;
+import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.curator.x.async.modeled.cached.CachedModeledFramework;
+import org.apache.curator.x.async.modeled.versioned.VersionedModeledFramework;
+import org.apache.zookeeper.data.Stat;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+
+public interface ModeledFramework<T>
+{
+    /**
+     * Return a new ModeledFramework for the given model
+     *
+     * @param client Curator client
+     * @param model the model
+     * @return new Modeled Curator instance
+     */
+    static <T> ModeledFramework<T> wrap(AsyncCuratorFramework client, ModelSpec<T> model)
+    {
+        return builder(client, model).build();
+    }
+
+    /**
+     * Start a new ModeledFrameworkBuilder for the given model
+     *
+     * @param client Curator client
+     * @param model the model
+     * @return builder
+     */
+    static <T> ModeledFrameworkBuilder<T> builder(AsyncCuratorFramework client, ModelSpec<T> model)
+    {
+        return new ModeledFrameworkBuilder<>(client, model);
+    }
+
+    /**
+     * Start a new ModeledFrameworkBuilder. A client and model must be provided prior to the instance
+     * being built via {@link org.apache.curator.x.async.modeled.ModeledFrameworkBuilder#withClient(org.apache.curator.x.async.AsyncCuratorFramework)}
+     * and {@link org.apache.curator.x.async.modeled.ModeledFrameworkBuilder#withModelSpec(ModelSpec)}
+     *
+     * @return builder
+     */
+    static <T> ModeledFrameworkBuilder<T> builder()
+    {
+        return new ModeledFrameworkBuilder<>();
+    }
+
+    /**
+     * <p>
+     *     Use an internally created cache as a front for this modeled instance. All read APIs use the internal
+     *     cache. i.e. read calls always use the cache instead of making direct queries. Note: you must call
+     *     {@link org.apache.curator.x.async.modeled.cached.CachedModeledFramework#start()} and
+     *     {@link org.apache.curator.x.async.modeled.cached.CachedModeledFramework#close()} to start/stop
+     * </p>
+     *
+     * <p>
+     *     Note: the root node (the main path of the model) is <em>not</em> cached. i.e. only nodes
+     *     below the root are cached.
+     * </p>
+     *
+     * <p>
+     *     Note: this method internally allocates an Executor for the cache and read methods. Use
+     *     {@link #cached(java.util.concurrent.ExecutorService)} if you'd like to provide your own executor service.
+     * </p>
+     *
+     * @return wrapped instance
+     */
+    CachedModeledFramework<T> cached();
+
+    /**
+     * Same as {@link #cached()} but allows for providing an executor service
+     *
+     * @param executor thread pool to use for the cache and for read operations
+     * @return wrapped instance
+     */
+    CachedModeledFramework<T> cached(ExecutorService executor);
+
+    /**
+     * Return mutator APIs that work with {@link org.apache.curator.x.async.modeled.versioned.Versioned} containers
+     *
+     * @return wrapped instance
+     */
+    VersionedModeledFramework<T> versioned();
+
+    /**
+     * Returns the client that was originally passed to {@link #wrap(org.apache.curator.x.async.AsyncCuratorFramework, ModelSpec)} or
+     * the builder.
+     *
+     * @return original client
+     */
+    AsyncCuratorFramework unwrap();
+
+    /**
+     * Return the model being used
+     *
+     * @return model
+     */
+    ModelSpec<T> modelSpec();
+
+    /**
+     * <p>
+     *     Return a new Modeled Curator instance with all the same options but applying to the given child node of this Modeled Curator's
+     *     path. E.g. if this Modeled Curator instance applies to "/a/b", calling <code>modeled.at("c")</code> returns an instance that applies to
+     *     "/a/b/c".
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of child or,
+     *     if it implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @param child child node.
+     * @return new Modeled Curator instance
+     */
+    ModeledFramework<T> child(Object child);
+
+    /**
+     * <p>
+     *     Return a new Modeled Curator instance with all the same options but applying to the parent node of this Modeled Curator's
+     *     path. E.g. if this Modeled Curator instance applies to "/a/b/c", calling <code>modeled.parent()</code> returns an instance that applies to
+     *     "/a/b".
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of child or,
+     *     if it implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @return new Modeled Curator instance
+     */
+    ModeledFramework<T> parent();
+
+    /**
+     * Return a Modeled Curator instance with all the same options but using the given path.
+     *
+     * @param path new path
+     * @return new Modeled Curator instance
+     */
+    ModeledFramework<T> withPath(ZPath path);
+
+    /**
+     * Create (or update depending on build options) a ZNode at this instance's path with a serialized
+     * version of the given model
+     *
+     * @param model model to write
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<String> set(T model);
+
+    /**
+     * Create (or update depending on build options) a ZNode at this instance's path with a serialized
+     * version of the given model
+     *
+     * @param model model to write
+     * @param version if data is being set instead of creating the node, the data version to use
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<String> set(T model, int version);
+
+    /**
+     * Create (or update depending on build options) a ZNode at this instance's path with a serialized
+     * form of the given model
+     *
+     * @param model model to write
+     * @param storingStatIn the stat for the new ZNode is stored here
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<String> set(T model, Stat storingStatIn);
+
+    /**
+     * Create (or update depending on build options) a ZNode at this instance's path with a serialized
+     * form of the given model
+     *
+     * @param model model to write
+     * @param version if data is being set instead of creating the node, the data version to use
+     * @param storingStatIn the stat for the new ZNode is stored here
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<String> set(T model, Stat storingStatIn, int version);
+
+    /**
+     * Read the ZNode at this instance's path and deserialize into a model
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<T> read();
+
+    /**
+     * Read the ZNode at this instance's path and deserialize into a model
+     *
+     * @param storingStatIn the stat for the new ZNode is stored here
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<T> read(Stat storingStatIn);
+
+    /**
+     * Read the ZNode at this instance's path and deserialize into a model
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<ZNode<T>> readAsZNode();
+
+    /**
+     * Update the ZNode at this instance's path with a serialized
+     * form of the given model passing "-1" for the update version
+     *
+     * @param model model to write
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<Stat> update(T model);
+
+    /**
+     * Update the ZNode at this instance's path with a serialized
+     * form of the given model passing the given update version
+     *
+     * @param model model to write
+     * @param version update version to use
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<Stat> update(T model, int version);
+
+    /**
+     * Delete the ZNode at this instance's path passing -1 for the delete version
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<Void> delete();
+
+    /**
+     * Delete the ZNode at this instance's path passing the given delete version
+     *
+     * @param version update version to use
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<Void> delete(int version);
+
+    /**
+     * Check to see if the ZNode at this instance's path exists
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<Stat> checkExists();
+
+    /**
+     * Return the child paths of this instance's path (in no particular order)
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<List<ZPath>> children();
+
+    /**
+     * Return the child paths of this instance's path (in no particular order)
+     * and deserialize into a models. IMPORTANT: this results in a ZooKeeper query
+     * for each child node returned. i.e. if the initial children() call returns
+     * 10 nodes an additional 10 ZooKeeper queries are made to get the data. Note:
+     * cannot be used if any of the {@link ModeledFrameworkBuilder#watched()} modes
+     * are used.
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<List<ZNode<T>>> childrenAsZNodes();
+
+    /**
+     * Create operation instance that can be passed among other operations to
+     * {@link #inTransaction(java.util.List)} to be executed as a single transaction. Note:
+     * due to ZooKeeper transaction limits, this is a _not_ a "set or update" operation but only
+     * a create operation and will generate an error if the node already exists.
+     *
+     * @param model the model
+     * @return operation
+     */
+    CuratorOp createOp(T model);
+
+    /**
+     * Update operation instance that can be passed among other operations to
+     * {@link #inTransaction(java.util.List)} to be executed as a single transaction.
+     *
+     * @param model the model
+     * @return operation
+     */
+    CuratorOp updateOp(T model);
+
+    /**
+     * Create operation instance that can be passed among other operations to
+     * {@link #inTransaction(java.util.List)} to be executed as a single transaction.
+     *
+     * @param model the model
+     * @param version update version to use
+     * @return operation
+     */
+    CuratorOp updateOp(T model, int version);
+
+    /**
+     * Delete operation instance that can be passed among other operations to
+     * {@link #inTransaction(java.util.List)} to be executed as a single transaction.
+     *
+     * @return operation
+     */
+    CuratorOp deleteOp();
+
+    /**
+     * Delete operation instance that can be passed among other operations to
+     * {@link #inTransaction(java.util.List)} to be executed as a single transaction.
+     *
+     * @param version delete version to use
+     * @return operation
+     */
+    CuratorOp deleteOp(int version);
+
+    /**
+     * Check exists operation instance that can be passed among other operations to
+     * {@link #inTransaction(java.util.List)} to be executed as a single transaction.
+     *
+     * @return operation
+     */
+    CuratorOp checkExistsOp();
+
+    /**
+     * Check exists operation instance that can be passed among other operations to
+     * {@link #inTransaction(java.util.List)} to be executed as a single transaction.
+     *
+     * @param version version to use
+     * @return operation
+     */
+    CuratorOp checkExistsOp(int version);
+
+    /**
+     * Invoke ZooKeeper to commit the given operations as a single transaction.
+     *
+     * @param operations operations that make up the transaction.
+     * @return AsyncStage instance for managing the completion
+     */
+    AsyncStage<List<CuratorTransactionResult>> inTransaction(List<CuratorOp> operations);
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java
new file mode 100644
index 0000000..2e8bec3
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java
@@ -0,0 +1,154 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import org.apache.curator.framework.api.CuratorEvent;
+import org.apache.curator.framework.api.UnhandledErrorListener;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.WatchMode;
+import org.apache.curator.x.async.modeled.details.ModeledFrameworkImpl;
+import org.apache.zookeeper.WatchedEvent;
+import java.util.Objects;
+import java.util.function.UnaryOperator;
+
+public class ModeledFrameworkBuilder<T>
+{
+    private AsyncCuratorFramework client;
+    private ModelSpec<T> modelSpec;
+    private WatchMode watchMode;
+    private UnaryOperator<WatchedEvent> watcherFilter;
+    private UnhandledErrorListener unhandledErrorListener;
+    private UnaryOperator<CuratorEvent> resultFilter;
+
+    /**
+     * Build a new ModeledFramework instance
+     *
+     * @return new ModeledFramework instance
+     */
+    public ModeledFramework<T> build()
+    {
+        return ModeledFrameworkImpl.build(
+            client,
+            modelSpec,
+            watchMode,
+            watcherFilter,
+            unhandledErrorListener,
+            resultFilter
+        );
+    }
+
+    /**
+     * Add watchers as appropriate to the Modeled Curator's ZNode using
+     * {@link org.apache.curator.x.async.WatchMode#stateChangeAndSuccess}
+     *
+     * @return this for chaining
+     * @see org.apache.curator.x.async.AsyncStage#event()
+     */
+    public ModeledFrameworkBuilder<T> watched()
+    {
+        this.watchMode = WatchMode.stateChangeAndSuccess;
+        return this;
+    }
+
+    /**
+     * Add watchers as appropriate using the given watchMode to the Modeled Curator's ZNode
+     *
+     * @param watchMode watcher style
+     * @return this for chaining
+     * @see org.apache.curator.x.async.AsyncStage#event()
+     */
+    public ModeledFrameworkBuilder<T> watched(WatchMode watchMode)
+    {
+        this.watchMode = watchMode;
+        return this;
+    }
+
+    /**
+     * Add watchers as appropriate using the given watchMode and filter to the Modeled Curator's ZNode
+     *
+     * @param watchMode watcher style
+     * @param watcherFilter filter
+     * @return this for chaining
+     * @see org.apache.curator.x.async.AsyncStage#event()
+     */
+    public ModeledFrameworkBuilder<T> watched(WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter)
+    {
+        this.watchMode = watchMode;
+        this.watcherFilter = watcherFilter;
+        return this;
+    }
+
+    /**
+     * Use the given unhandledErrorListener for operations on the Modeled Curator's ZNode
+     *
+     * @param unhandledErrorListener listener
+     * @return this for chaining
+     */
+    public ModeledFrameworkBuilder<T> withUnhandledErrorListener(UnhandledErrorListener unhandledErrorListener)
+    {
+        this.unhandledErrorListener = unhandledErrorListener;
+        return this;
+    }
+
+    /**
+     * Use the given result filter for operations on the Modeled Curator's ZNode
+     *
+     * @param resultFilter filter
+     * @return this for chaining
+     */
+    public ModeledFrameworkBuilder<T> withResultFilter(UnaryOperator<CuratorEvent> resultFilter)
+    {
+        this.resultFilter = resultFilter;
+        return this;
+    }
+
+    /**
+     * Change the model spec to use
+     *
+     * @param modelSpec model spec
+     * @return this for chaining
+     */
+    public ModeledFrameworkBuilder<T> withModelSpec(ModelSpec<T> modelSpec)
+    {
+        this.modelSpec = Objects.requireNonNull(modelSpec, "modelSpec cannot be null");
+        return this;
+    }
+
+    /**
+     * Change the client to use
+     *
+     * @param client new client
+     * @return this for chaining
+     */
+    public ModeledFrameworkBuilder<T> withClient(AsyncCuratorFramework client)
+    {
+        this.client = Objects.requireNonNull(client, "client cannot be null");
+        return this;
+    }
+
+    ModeledFrameworkBuilder()
+    {
+    }
+
+    ModeledFrameworkBuilder(AsyncCuratorFramework client, ModelSpec<T> modelSpec)
+    {
+        this.client = Objects.requireNonNull(client, "client cannot be null");
+        this.modelSpec = Objects.requireNonNull(modelSpec, "modelSpec cannot be null");
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/NodeName.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/NodeName.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/NodeName.java
new file mode 100644
index 0000000..6bc3be3
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/NodeName.java
@@ -0,0 +1,39 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+/**
+ * Used by the various "resolved" methods and "at" methods.
+ * If the argument to one of these methods implements this interface,
+ * the {@link #nodeName()} method is used instead of calling <code>toString()</code>
+ */
+@FunctionalInterface
+public interface NodeName
+{
+    String nodeName();
+
+    static String nameFrom(Object obj)
+    {
+        if ( obj instanceof NodeName )
+        {
+            return ((NodeName)obj).nodeName();
+        }
+        return String.valueOf(obj);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/Resolvable.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/Resolvable.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/Resolvable.java
new file mode 100644
index 0000000..209ffa1
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/Resolvable.java
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import java.util.Arrays;
+import java.util.List;
+
+public interface Resolvable
+{
+    /**
+     * When creating paths, any node in the path can be set to {@link ZPath#parameter()}.
+     * At runtime, the ZPath can be "resolved" by replacing these nodes with values.
+     *
+     * @param parameters list of replacements. Must have be the same length as the number of
+     *                   parameter nodes in the path
+     * @return new resolved ZPath
+     */
+    default Object resolved(Object... parameters)
+    {
+        return resolved(Arrays.asList(parameters));
+    }
+
+    /**
+     * When creating paths, any node in the path can be set to {@link ZPath#parameter()}.
+     * At runtime, the ZPath can be "resolved" by replacing these nodes with values.
+     *
+     * @param parameters list of replacements. Must have be the same length as the number of
+     *                   parameter nodes in the path
+     * @return new resolved ZPath
+     */
+    Object resolved(List<Object> parameters);
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZNode.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZNode.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZNode.java
new file mode 100644
index 0000000..0d34d82
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZNode.java
@@ -0,0 +1,74 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.zookeeper.data.Stat;
+import java.util.List;
+import java.util.concurrent.CompletionStage;
+import java.util.stream.Collectors;
+
+/**
+ * Abstracts a ZooKeeper node
+ */
+public interface ZNode<T>
+{
+    /**
+     * The path of the node
+     *
+     * @return path
+     */
+    ZPath path();
+
+    /**
+     * The node's last known stat if available
+     *
+     * @return stat
+     */
+    Stat stat();
+
+    /**
+     * The node's current model
+     *
+     * @return model
+     */
+    T model();
+
+    /**
+     * Utility that modifies an async stage of znodes into an async stage of models
+     *
+     * @param from original stage
+     * @return stage of models
+     */
+    static <T> CompletionStage<List<T>> models(AsyncStage<List<ZNode<T>>> from)
+    {
+        return from.thenApply(nodes -> nodes.stream().map(ZNode::model).collect(Collectors.toList()));
+    }
+
+    /**
+     * Utility that modifies an async stage of a znode into an async stage of a model
+     *
+     * @param from original stage
+     * @return stage of a model
+     */
+    static <T> CompletionStage<T> model(AsyncStage<ZNode<T>> from)
+    {
+        return from.thenApply(ZNode::model);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZPath.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZPath.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZPath.java
new file mode 100644
index 0000000..70ac536
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ZPath.java
@@ -0,0 +1,279 @@
+/**
+ * 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.
+ */
+package org.apache.curator.x.async.modeled;
+
+import org.apache.curator.x.async.modeled.details.ZPathImpl;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.UnaryOperator;
+import java.util.regex.Pattern;
+
+import static org.apache.curator.utils.ZKPaths.PATH_SEPARATOR;
+
+/**
+ * Abstracts a ZooKeeper ZNode path
+ */
+public interface ZPath extends Resolvable
+{
+    /**
+     * The root path: "/"
+     */
+    ZPath root = ZPathImpl.root;
+
+    /**
+     * Returns the special node name that can be used for replacements at runtime
+     * via {@link #resolved(Object...)} when passed via the various <code>from()</code> methods
+     */
+    static String parameter()
+    {
+        return parameter("id");
+    }
+
+    /**
+     * Same as {@link #parameter()} but allows you to specify an alternate code/name. This name
+     * has no effect and is only for debugging purposes. When <code>toString()</code> is called
+     * on ZPaths, this code shows.
+     */
+    static String parameter(String name)
+    {
+        return PATH_SEPARATOR + "{" + name + "}";
+    }
+
+    /**
+     * Take a string path and return a ZPath
+     *
+     * @param fullPath the path to parse
+     * @return ZPath
+     * @throws IllegalArgumentException if the path is invalid
+     */
+    static ZPath parse(String fullPath)
+    {
+        return ZPathImpl.parse(fullPath, s -> s);
+    }
+
+    /**
+     * Take a string path and return a ZPath. Each part of the path
+     * that is <code>{XXXX}</code> is replaced with {@link #parameter()}.
+     * E.g. <code>parseWithIds("/one/two/{first}/three/{second}")</code> is the equivalent
+     * of calling <code>ZPath.from("one", "two", parameter(), "three", parameter())</code>
+     *
+     * @param fullPath the path to parse
+     * @return ZPath
+     * @throws IllegalArgumentException if the path is invalid
+     */
+    static ZPath parseWithIds(String fullPath)
+    {
+        return ZPathImpl.parse(fullPath, s -> isId(s) ? (PATH_SEPARATOR + s) : s); // TODO
+    }
+
+    /**
+     * Return true if the given string conforms to the "{XXXX}" ID pattern
+     *
+     * @param s string to check
+     * @return true/false
+     */
+    static boolean isId(String s)
+    {
+        return s.startsWith("{") && s.endsWith("}");
+    }
+
+    /**
+     * Take a ZNode string path and return a ZPath
+     *
+     * @param fullPath the path to parse
+     * @param nameFilter each part of the path is passed through this filter
+     * @return ZPath
+     * @throws IllegalArgumentException if the path is invalid
+     */
+    static ZPath parse(String fullPath, UnaryOperator<String> nameFilter)
+    {
+        return ZPathImpl.parse(fullPath, nameFilter);
+    }
+
+    /**
+     * Convert individual path names into a ZPath. E.g.
+     * <code>ZPath.from("my", "full", "path")</code>. Any/all of the names can be passed as
+     * {@link #parameter()} so that the path can be resolved later using
+     * of the <code>resolved()</code> methods.
+     *
+     * @param names path names
+     * @return ZPath
+     * @throws IllegalArgumentException if any of the names is invalid
+     */
+    static ZPath from(String... names)
+    {
+        return ZPathImpl.from(names);
+    }
+
+    /**
+     * Convert individual path names into a ZPath. Any/all of the names can be passed as
+     * {@link #parameter()} so that the path can be resolved later using
+     * of the <code>resolved()</code> methods.
+     *
+     * @param names path names
+     * @return ZPath
+     * @throws IllegalArgumentException if any of the names is invalid
+     */
+    static ZPath from(List<String> names)
+    {
+        return ZPathImpl.from(names);
+    }
+
+    /**
+     * Convert individual path names into a ZPath starting at the given base. E.g.
+     * if base is "/home/base" <code>ZPath.from(base, "my", "full", "path")</code>
+     * would be "/home/base/my/full/path". Any/all of the names can be passed as
+     * {@link #parameter()} so that the path can be resolved later using
+     * of the <code>resolved()</code> methods.
+     *
+     * @param base base/starting path
+     * @param names path names
+     * @return ZPath
+     * @throws IllegalArgumentException if any of the names is invalid
+     */
+    static ZPath from(ZPath base, String... names)
+    {
+        return ZPathImpl.from(base, names);
+    }
+
+    /**
+     * Convert individual path names into a ZPath starting at the given base. Any/all of the names can be passed as
+     * {@link #parameter()} so that the path can be resolved later using
+     * of the <code>resolved()</code> methods.
+     *
+     * @param base base/starting path
+     * @param names path names
+     * @return ZPath
+     * @throws IllegalArgumentException if any of the names is invalid
+     */
+    static ZPath from(ZPath base, List<String> names)
+    {
+        return ZPathImpl.from(base, names);
+    }
+
+    /**
+     * <p>
+     *     When creating paths, any node in the path can be set to {@link #parameter()}.
+     *     At runtime, the ZPath can be "resolved" by replacing these nodes with values.
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of the parameter object or,
+     *     if the object implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @param parameters list of replacements. Must have be the same length as the number of
+     *                   parameter nodes in the path
+     * @return new resolved ZPath
+     */
+    @Override
+    default ZPath resolved(Object... parameters)
+    {
+        return resolved(Arrays.asList(parameters));
+    }
+
+    /**
+     * <p>
+     *     When creating paths, any node in the path can be set to {@link #parameter()}.
+     *     At runtime, the ZPath can be "resolved" by replacing these nodes with values.
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of the parameter object or,
+     *     if the object implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @param parameters list of replacements. Must have be the same length as the number of
+     *                   parameter nodes in the path
+     * @return new resolved ZPath
+     */
+    @Override
+    ZPath resolved(List<Object> parameters);
+
+    /**
+     * <p>
+     *     Return a ZPath that represents a child ZNode of this ZPath. e.g.
+     *     <code>ZPath.from("a", "b").at("c")</code> represents the path "/a/b/c"
+     * </p>
+     *
+     * <p>
+     *     The replacement is the <code>toString()</code> value of child or,
+     *     if it implements {@link org.apache.curator.x.async.modeled.NodeName},
+     *     the value of <code>nodeName()</code>.
+     * </p>
+     *
+     * @param child child node name
+     * @return ZPath
+     */
+    ZPath child(Object child);
+
+    /**
+     * Return this ZPath's parent
+     *
+     * @return parent ZPath
+     * @throws java.util.NoSuchElementException if this is the root ZPath
+     */
+    ZPath parent();
+
+    /**
+     * Return true/false if this is the root ZPath
+     *
+     * @return true false
+     */
+    boolean isRoot();
+
+    /**
+     * Return true if this path is fully resolved (i.e. has no unresoled parameters)
+     *
+     * @return true/false
+     */
+    boolean isResolved();
+
+    /**
+     * Return true if this path starts with the given path. i.e.
+     * <code>ZPath.from("/one/two/three").startsWith(ZPath.from("/one/two"))</code> returns true
+     *
+     * @param path base path
+     * @return true/false
+     */
+    boolean startsWith(ZPath path);
+
+    /**
+     * The string full path that this ZPath represents
+     *
+     * @return full path
+     */
+    String fullPath();
+
+    /**
+     * The node name at this ZPath
+     *
+     * @return name
+     */
+    String nodeName();
+
+    /**
+     * Return a regex Pattern useful for using in {@link org.apache.curator.framework.schema.Schema}
+     *
+     * @return pattern for this path
+     */
+    Pattern toSchemaPathPattern();
+}