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:33 UTC

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

Repository: curator
Updated Branches:
  refs/heads/master 8b28b1208 -> 0f5d10da3


http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-rpc/src/site/site.xml
----------------------------------------------------------------------
diff --git a/curator-x-rpc/src/site/site.xml b/curator-x-rpc/src/site/site.xml
index 135c902..fca1e73 100644
--- a/curator-x-rpc/src/site/site.xml
+++ b/curator-x-rpc/src/site/site.xml
@@ -25,7 +25,7 @@
             <link rel="stylesheet" href="../css/site.css" />
             <script type="text/javascript">
                 $(function(){
-                $('a[title="Curator Async"]').parent().addClass("active");
+                $('a[title="Curator RPC Proxy"]').parent().addClass("active");
                 });
             </script>
         </head>

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 73346f9..aa8fd86 100644
--- a/pom.xml
+++ b/pom.xml
@@ -364,6 +364,18 @@
             </dependency>
 
             <dependency>
+                <groupId>org.apache.curator</groupId>
+                <artifactId>curator-x-async</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.apache.curator</groupId>
+                <artifactId>curator-x-rpc</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
+            <dependency>
                 <groupId>org.apache.commons</groupId>
                 <artifactId>commons-math</artifactId>
                 <version>${commons-math-version}</version>

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/src/site/site.xml
----------------------------------------------------------------------
diff --git a/src/site/site.xml b/src/site/site.xml
index 222ffde..83ddd46 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -73,6 +73,8 @@
             <item name="Framework" href="curator-framework/index.html"/>
             <item name="Utilities" href="utilities.html"/>
             <item name="Client" href="curator-client/index.html"/>
+            <item name="Java 8/Async" href="curator-x-async/index.html"/>
+            <item name="Strongly Typed Models" href="curator-x-async/modeled.html"/>
             <item name="Schema Support" href="curator-framework/schema.html"/>
         </menu>
 
@@ -94,7 +96,7 @@
             <item name="Service Discovery" href="curator-x-discovery/index.html"/>
             <item name="Service Discovery Server" href="curator-x-discovery-server/index.html"/>
             <item name="Curator RPC Proxy" href="curator-x-rpc/index.html"/>
-            <item name="Curator Async" href="curator-x-async/index.html"/>
+            <item name="Curator Java 8/Async" href="curator-x-async/index.html"/>
         </menu>
 
         <menu name="Community" inherit="top">


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

Posted by ra...@apache.org.
http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/CachedModeledFramework.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/CachedModeledFramework.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/CachedModeledFramework.java
new file mode 100644
index 0000000..8acbebb
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/CachedModeledFramework.java
@@ -0,0 +1,123 @@
+/**
+ * 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.cached;
+
+import org.apache.curator.framework.listen.Listenable;
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ZNode;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.zookeeper.data.Stat;
+import java.io.Closeable;
+import java.util.List;
+
+public interface CachedModeledFramework<T> extends ModeledFramework<T>, Closeable
+{
+    /**
+     * Return the cache instance
+     *
+     * @return cache
+     */
+    ModeledCache<T> cache();
+
+    /**
+     * Returns a view of this instance that uses the CachedModeledFramework's executor
+     * for all default async completion operations. i.e. when you use, for example,
+     * {@link java.util.concurrent.CompletionStage#handleAsync(java.util.function.BiFunction)}
+     * this instance's executor is used instead of <code>ForkJoinPool.commonPool()</code>.
+     *
+     * @return view
+     */
+    CachedModeledFramework<T> asyncDefault();
+
+    /**
+     * Start the internally created cache
+     */
+    void start();
+
+    /**
+     * Close/stop the internally created cache
+     */
+    @Override
+    void close();
+
+    /**
+     * Return the listener container so that you can add/remove listeners
+     *
+     * @return listener container
+     */
+    Listenable<ModeledCacheListener<T>> listenable();
+
+    /**
+     * Same as {@link org.apache.curator.x.async.modeled.ModeledFramework#childrenAsZNodes()}
+     * but always reads from cache - i.e. no additional queries to ZooKeeper are made
+     *
+     * @return AsyncStage stage
+     */
+    @Override
+    AsyncStage<List<ZNode<T>>> childrenAsZNodes();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    CachedModeledFramework<T> child(Object child);
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    CachedModeledFramework<T> withPath(ZPath path);
+
+    /**
+     * Same as {@link #read()} except that if the cache does not have a value
+     * for this path a direct query is made.
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<T> readThrough();
+
+    /**
+     * Same as {@link #read(org.apache.zookeeper.data.Stat)} except that if the cache does not have a value
+     * for this path a direct query is made.
+     *
+     * @param storingStatIn the stat for the new ZNode is stored here
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<T> readThrough(Stat storingStatIn);
+
+    /**
+     * Same as {@link #readAsZNode()} except that if the cache does not have a value
+     * for this path a direct query is made.
+     *
+     *
+     * @return AsyncStage
+     * @see org.apache.curator.x.async.AsyncStage
+     */
+    AsyncStage<ZNode<T>> readThroughAsZNode();
+
+    /**
+     * Return the instances of the base path of this cached framework
+     *
+     * @return listing of all models in the base path
+     */
+    AsyncStage<List<T>> list();
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCache.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCache.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCache.java
new file mode 100644
index 0000000..6677268
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCache.java
@@ -0,0 +1,46 @@
+/**
+ * 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.cached;
+
+import org.apache.curator.x.async.modeled.ZNode;
+import org.apache.curator.x.async.modeled.ZPath;
+import java.util.Map;
+import java.util.Optional;
+
+public interface ModeledCache<T>
+{
+    /**
+     * Return the modeled current data for the given path. There are no guarantees of accuracy. This is
+     * merely the most recent view of the data. If there is no node at the given path,
+     * {@link java.util.Optional#empty()} is returned.
+     *
+     * @param path path to the node to check
+     * @return data if the node is alive, or empty
+     */
+    Optional<ZNode<T>> currentData(ZPath path);
+
+    /**
+     * Return the modeled current set of children at the given path, mapped by child name. There are no
+     * guarantees of accuracy; this is merely the most recent view of the data.
+     *
+     * @param path path to the node to check
+     * @return a possibly-empty map of children if the node is alive
+     */
+    Map<ZPath, ZNode<T>> currentChildren(ZPath path);
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCacheListener.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCacheListener.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCacheListener.java
new file mode 100644
index 0000000..42498c0
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/cached/ModeledCacheListener.java
@@ -0,0 +1,106 @@
+/**
+ * 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.cached;
+
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.zookeeper.data.Stat;
+import org.slf4j.LoggerFactory;
+
+@FunctionalInterface
+public interface ModeledCacheListener<T>
+{
+    enum Type
+    {
+        /**
+         * A child was added to the path
+         */
+        NODE_ADDED,
+
+        /**
+         * A child's data was changed
+         */
+        NODE_UPDATED,
+
+        /**
+         * A child was removed from the path
+         */
+        NODE_REMOVED
+    }
+
+    /**
+     * The given path was added, updated or removed
+     *
+     * @param type action type
+     * @param path the path
+     * @param stat the node's stat (previous stat for removal)
+     * @param model the node's model (previous model for removal)
+     */
+    void accept(Type type, ZPath path, Stat stat, T model);
+
+    /**
+     * The cache has finished initializing
+     */
+    default void initialized()
+    {
+        // NOP
+    }
+
+    /**
+     * Called when there is an exception processing a message from the internal cache. This is most
+     * likely due to a de-serialization problem.
+     *
+     * @param e the exception
+     */
+    default void handleException(Exception e)
+    {
+        LoggerFactory.getLogger(getClass()).error("Could not process cache message", e);
+    }
+
+    /**
+     * Returns a version of this listener that only begins calling
+     * {@link #accept(org.apache.curator.x.async.modeled.cached.ModeledCacheListener.Type, org.apache.curator.x.async.modeled.ZPath, org.apache.zookeeper.data.Stat, Object)}
+     * once {@link #initialized()} has been called. i.e. changes that occur as the cache is initializing are not sent
+     * to the listener
+     *
+     * @return wrapped listener
+     */
+    default ModeledCacheListener<T> postInitializedOnly()
+    {
+        return new ModeledCacheListener<T>()
+        {
+            private volatile boolean isInitialized = false;
+
+            @Override
+            public void accept(Type type, ZPath path, Stat stat, T model)
+            {
+                if ( isInitialized )
+                {
+                    ModeledCacheListener.this.accept(type, path, stat, model);
+                }
+            }
+
+            @Override
+            public void initialized()
+            {
+                isInitialized = true;
+                ModeledCacheListener.this.initialized();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/CachedModeledFrameworkImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/CachedModeledFrameworkImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/CachedModeledFrameworkImpl.java
new file mode 100644
index 0000000..2a7fd5f
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/CachedModeledFrameworkImpl.java
@@ -0,0 +1,342 @@
+/**
+ * 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.details;
+
+import org.apache.curator.framework.api.transaction.CuratorOp;
+import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
+import org.apache.curator.framework.listen.Listenable;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ZNode;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.curator.x.async.modeled.cached.CachedModeledFramework;
+import org.apache.curator.x.async.modeled.cached.ModeledCache;
+import org.apache.curator.x.async.modeled.cached.ModeledCacheListener;
+import org.apache.curator.x.async.modeled.versioned.VersionedModeledFramework;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.data.Stat;
+import org.apache.zookeeper.server.DataTree;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+class CachedModeledFrameworkImpl<T> implements CachedModeledFramework<T>
+{
+    private final ModeledFramework<T> client;
+    private final ModeledCacheImpl<T> cache;
+    private final Executor executor;
+    private final boolean asyncDefaultMode;
+
+    CachedModeledFrameworkImpl(ModeledFramework<T> client, ExecutorService executor)
+    {
+        this(client, new ModeledCacheImpl<>(client.unwrap().unwrap(), client.modelSpec(), executor), executor, false);
+    }
+
+    private CachedModeledFrameworkImpl(ModeledFramework<T> client, ModeledCacheImpl<T> cache, Executor executor, boolean asyncDefaultMode)
+    {
+        this.client = client;
+        this.cache = cache;
+        this.executor = executor;
+        this.asyncDefaultMode = asyncDefaultMode;
+    }
+
+    @Override
+    public ModeledCache<T> cache()
+    {
+        return cache;
+    }
+
+    @Override
+    public CachedModeledFramework<T> asyncDefault()
+    {
+        return new CachedModeledFrameworkImpl<>(client, cache, executor, true);
+    }
+
+    @Override
+    public void start()
+    {
+        cache.start();
+    }
+
+    @Override
+    public void close()
+    {
+        cache.close();
+    }
+
+    @Override
+    public Listenable<ModeledCacheListener<T>> listenable()
+    {
+        return cache.listenable();
+    }
+
+    @Override
+    public CachedModeledFramework<T> cached()
+    {
+        throw new UnsupportedOperationException("Already a cached instance");
+    }
+
+    @Override
+    public CachedModeledFramework<T> cached(ExecutorService executor)
+    {
+        throw new UnsupportedOperationException("Already a cached instance");
+    }
+
+    @Override
+    public VersionedModeledFramework<T> versioned()
+    {
+        return new VersionedModeledFrameworkImpl<>(this);
+    }
+
+    @Override
+    public AsyncCuratorFramework unwrap()
+    {
+        return client.unwrap();
+    }
+
+    @Override
+    public ModelSpec<T> modelSpec()
+    {
+        return client.modelSpec();
+    }
+
+    @Override
+    public CachedModeledFramework<T> child(Object child)
+    {
+        return new CachedModeledFrameworkImpl<>(client.child(child), cache, executor, asyncDefaultMode);
+    }
+
+    @Override
+    public ModeledFramework<T> parent()
+    {
+        throw new UnsupportedOperationException("Not supported for CachedModeledFramework. Instead, call parent() on the ModeledFramework before calling cached()");
+    }
+
+    @Override
+    public CachedModeledFramework<T> withPath(ZPath path)
+    {
+        return new CachedModeledFrameworkImpl<>(client.withPath(path), cache, executor, asyncDefaultMode);
+    }
+
+    @Override
+    public AsyncStage<String> set(T model)
+    {
+        return client.set(model);
+    }
+
+    @Override
+    public AsyncStage<String> set(T model, Stat storingStatIn)
+    {
+        return client.set(model, storingStatIn);
+    }
+
+    @Override
+    public AsyncStage<String> set(T model, Stat storingStatIn, int version)
+    {
+        return client.set(model, storingStatIn, version);
+    }
+
+    @Override
+    public AsyncStage<String> set(T model, int version)
+    {
+        return client.set(model, version);
+    }
+
+    @Override
+    public AsyncStage<T> read()
+    {
+        return internalRead(ZNode::model, this::exceptionally);
+    }
+
+    @Override
+    public AsyncStage<T> read(Stat storingStatIn)
+    {
+        return internalRead(n -> {
+            if ( storingStatIn != null )
+            {
+                DataTree.copyStat(n.stat(), storingStatIn);
+            }
+            return n.model();
+        }, this::exceptionally);
+    }
+
+    @Override
+    public AsyncStage<ZNode<T>> readAsZNode()
+    {
+        return internalRead(Function.identity(), this::exceptionally);
+    }
+
+    @Override
+    public AsyncStage<T> readThrough()
+    {
+        return internalRead(ZNode::model, client::read);
+    }
+
+    @Override
+    public AsyncStage<T> readThrough(Stat storingStatIn)
+    {
+        return internalRead(ZNode::model, () -> client.read(storingStatIn));
+    }
+
+    @Override
+    public AsyncStage<ZNode<T>> readThroughAsZNode()
+    {
+        return internalRead(Function.identity(), client::readAsZNode);
+    }
+
+    @Override
+    public AsyncStage<List<T>> list()
+    {
+        List<T> children = cache.currentChildren()
+            .values()
+            .stream()
+            .map(ZNode::model)
+            .collect(Collectors.toList());
+        return asyncDefaultMode ? ModelStage.asyncCompleted(children, executor) : ModelStage.completed(children);
+    }
+
+    @Override
+    public AsyncStage<Stat> update(T model)
+    {
+        return client.update(model);
+    }
+
+    @Override
+    public AsyncStage<Stat> update(T model, int version)
+    {
+        return client.update(model, version);
+    }
+
+    @Override
+    public AsyncStage<Void> delete()
+    {
+        return client.delete();
+    }
+
+    @Override
+    public AsyncStage<Void> delete(int version)
+    {
+        return client.delete(version);
+    }
+
+    @Override
+    public AsyncStage<Stat> checkExists()
+    {
+        ZPath path = client.modelSpec().path();
+        Optional<ZNode<T>> data = cache.currentData(path);
+        return data.map(node -> completed(node.stat())).orElseGet(() -> completed(null));
+    }
+
+    @Override
+    public AsyncStage<List<ZPath>> children()
+    {
+        List<ZPath> paths = cache.currentChildren(client.modelSpec().path())
+            .keySet()
+            .stream()
+            .filter(path -> path.equals(cache.basePath()))
+            .collect(Collectors.toList());
+        return completed(paths);
+    }
+
+    @Override
+    public AsyncStage<List<ZNode<T>>> childrenAsZNodes()
+    {
+        List<ZNode<T>> nodes = cache.currentChildren(client.modelSpec().path())
+            .entrySet()
+            .stream()
+            .filter(e -> e.getKey().equals(cache.basePath()))
+            .map(Map.Entry::getValue)
+            .collect(Collectors.toList());
+        return completed(nodes);
+    }
+
+    @Override
+    public CuratorOp createOp(T model)
+    {
+        return client.createOp(model);
+    }
+
+    @Override
+    public CuratorOp updateOp(T model)
+    {
+        return client.updateOp(model);
+    }
+
+    @Override
+    public CuratorOp updateOp(T model, int version)
+    {
+        return client.updateOp(model, version);
+    }
+
+    @Override
+    public CuratorOp deleteOp()
+    {
+        return client.deleteOp();
+    }
+
+    @Override
+    public CuratorOp deleteOp(int version)
+    {
+        return client.deleteOp(version);
+    }
+
+    @Override
+    public CuratorOp checkExistsOp()
+    {
+        return client.checkExistsOp();
+    }
+
+    @Override
+    public CuratorOp checkExistsOp(int version)
+    {
+        return client.checkExistsOp(version);
+    }
+
+    @Override
+    public AsyncStage<List<CuratorTransactionResult>> inTransaction(List<CuratorOp> operations)
+    {
+        return client.inTransaction(operations);
+    }
+
+    private <U> AsyncStage<U> completed(U value)
+    {
+        return asyncDefaultMode ? ModelStage.asyncCompleted(value, executor) : ModelStage.completed(value);
+    }
+
+    private <U> AsyncStage<U> exceptionally()
+    {
+        KeeperException.NoNodeException exception = new KeeperException.NoNodeException(client.modelSpec().path().fullPath());
+        return asyncDefaultMode ? ModelStage.asyncExceptionally(exception, executor) : ModelStage.exceptionally(exception);
+    }
+
+    private <U> AsyncStage<U> internalRead(Function<ZNode<T>, U> resolver, Supplier<AsyncStage<U>> elseProc)
+    {
+        ZPath path = client.modelSpec().path();
+        Optional<ZNode<T>> data = cache.currentData(path);
+        return data.map(node -> completed(resolver.apply(node)))
+            .orElseGet(elseProc);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelSpecImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelSpecImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelSpecImpl.java
new file mode 100644
index 0000000..58405eb
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelSpecImpl.java
@@ -0,0 +1,239 @@
+/**
+ * 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.details;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.apache.curator.framework.schema.Schema;
+import org.apache.curator.framework.schema.SchemaValidator;
+import org.apache.curator.framework.schema.SchemaViolation;
+import org.apache.curator.x.async.api.CreateOption;
+import org.apache.curator.x.async.api.DeleteOption;
+import org.apache.curator.x.async.modeled.ModelSerializer;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+public class ModelSpecImpl<T> implements ModelSpec<T>, SchemaValidator
+{
+    private final ZPath path;
+    private final ModelSerializer<T> serializer;
+    private final CreateMode createMode;
+    private final List<ACL> aclList;
+    private final Set<CreateOption> createOptions;
+    private final Set<DeleteOption> deleteOptions;
+    private final long ttl;
+    private volatile Schema schema = null;
+
+    public ModelSpecImpl(ZPath path, ModelSerializer<T> serializer, CreateMode createMode, List<ACL> aclList, Set<CreateOption> createOptions, Set<DeleteOption> deleteOptions, long ttl)
+    {
+        this.path = Objects.requireNonNull(path, "path cannot be null");
+        this.serializer = Objects.requireNonNull(serializer, "serializer cannot be null");
+        this.createMode = Objects.requireNonNull(createMode, "createMode cannot be null");
+        this.aclList = ImmutableList.copyOf(Objects.requireNonNull(aclList, "aclList cannot be null"));
+        this.createOptions = ImmutableSet.copyOf(Objects.requireNonNull(createOptions, "createOptions cannot be null"));
+        this.deleteOptions = ImmutableSet.copyOf(Objects.requireNonNull(deleteOptions, "deleteOptions cannot be null"));
+        this.ttl = ttl;
+    }
+
+    @Override
+    public ModelSpec<T> child(Object child)
+    {
+        return withPath(path.child(child));
+    }
+
+    @Override
+    public ModelSpec<T> parent()
+    {
+        return withPath(path.parent());
+    }
+
+    @Override
+    public ModelSpec<T> resolved(Object... parameters)
+    {
+        return withPath(path.resolved(parameters));
+    }
+
+    @Override
+    public ModelSpec<T> resolved(List<Object> parameters)
+    {
+        return withPath(path.resolved(parameters));
+    }
+
+    @Override
+    public ModelSpec<T> withPath(ZPath newPath)
+    {
+        return new ModelSpecImpl<>(newPath, serializer, createMode, aclList, createOptions, deleteOptions, ttl);
+    }
+
+    @Override
+    public ZPath path()
+    {
+        return path;
+    }
+
+    @Override
+    public ModelSerializer<T> serializer()
+    {
+        return serializer;
+    }
+
+    @Override
+    public CreateMode createMode()
+    {
+        return createMode;
+    }
+
+    @Override
+    public List<ACL> aclList()
+    {
+        return aclList;
+    }
+
+    @Override
+    public Set<CreateOption> createOptions()
+    {
+        return createOptions;
+    }
+
+    @Override
+    public Set<DeleteOption> deleteOptions()
+    {
+        return deleteOptions;
+    }
+
+    @Override
+    public long ttl()
+    {
+        return ttl;
+    }
+
+    @Override
+    public Schema schema()
+    {
+        if ( schema == null )
+        {
+            schema = Schema.builder(path.toSchemaPathPattern())
+                .dataValidator(this)
+                .ephemeral(createMode.isEphemeral() ? Schema.Allowance.MUST : Schema.Allowance.CANNOT)
+                .canBeDeleted(true)
+                .sequential(createMode.isSequential() ? Schema.Allowance.MUST : Schema.Allowance.CANNOT)
+                .watched(Schema.Allowance.CAN)
+                .build();
+        }
+        return schema;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        ModelSpecImpl<?> modelSpec = (ModelSpecImpl<?>)o;
+
+        if ( ttl != modelSpec.ttl )
+        {
+            return false;
+        }
+        if ( !path.equals(modelSpec.path) )
+        {
+            return false;
+        }
+        if ( !serializer.equals(modelSpec.serializer) )
+        {
+            return false;
+        }
+        if ( createMode != modelSpec.createMode )
+        {
+            return false;
+        }
+        if ( !aclList.equals(modelSpec.aclList) )
+        {
+            return false;
+        }
+        if ( !createOptions.equals(modelSpec.createOptions) )
+        {
+            return false;
+        }
+        //noinspection SimplifiableIfStatement
+        if ( !deleteOptions.equals(modelSpec.deleteOptions) )
+        {
+            return false;
+        }
+        return schema.equals(modelSpec.schema);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = path.hashCode();
+        result = 31 * result + serializer.hashCode();
+        result = 31 * result + createMode.hashCode();
+        result = 31 * result + aclList.hashCode();
+        result = 31 * result + createOptions.hashCode();
+        result = 31 * result + deleteOptions.hashCode();
+        result = 31 * result + (int)(ttl ^ (ttl >>> 32));
+        result = 31 * result + schema.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "ModelSpecImpl{" + "path=" + path + ", serializer=" + serializer + ", createMode=" + createMode + ", aclList=" + aclList + ", createOptions=" + createOptions + ", deleteOptions=" + deleteOptions + ", ttl=" + ttl + ", schema=" + schema + '}';
+    }
+
+    @Override
+    public boolean isValid(Schema schema, String path, byte[] data, List<ACL> acl)
+    {
+        if ( acl != null )
+        {
+            List<ACL> localAclList = (aclList.size() > 0) ? aclList : ZooDefs.Ids.OPEN_ACL_UNSAFE;
+            if ( !acl.equals(localAclList) )
+            {
+                throw new SchemaViolation(schema, new SchemaViolation.ViolatorData(path, data, acl), "ACLs do not match model ACLs");
+            }
+        }
+
+        if ( data != null )
+        {
+            try
+            {
+                serializer.deserialize(data);
+            }
+            catch ( RuntimeException e )
+            {
+                throw new SchemaViolation(schema, new SchemaViolation.ViolatorData(path, data, acl), "Data cannot be deserialized into a model");
+            }
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelStage.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelStage.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelStage.java
new file mode 100644
index 0000000..27047ec
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModelStage.java
@@ -0,0 +1,171 @@
+/**
+ * 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.details;
+
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.zookeeper.WatchedEvent;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+class ModelStage<T> extends CompletableFuture<T> implements AsyncStage<T>
+{
+    private final CompletionStage<WatchedEvent> event;
+
+    static <U> ModelStage<U> make()
+    {
+        return new ModelStage<>(null);
+    }
+
+    static <U> ModelStage<U> make(CompletionStage<WatchedEvent> event)
+    {
+        return new ModelStage<>(event);
+    }
+
+    static <U> ModelStage<U> completed(U value)
+    {
+        ModelStage<U> stage = new ModelStage<>(null);
+        stage.complete(value);
+        return stage;
+    }
+
+    static <U> ModelStage<U> exceptionally(Exception e)
+    {
+        ModelStage<U> stage = new ModelStage<>(null);
+        stage.completeExceptionally(e);
+        return stage;
+    }
+
+    static <U> ModelStage<U> async(Executor executor)
+    {
+        return new AsyncModelStage<>(executor);
+    }
+
+    static <U> ModelStage<U> asyncCompleted(U value, Executor executor)
+    {
+        ModelStage<U> stage = new AsyncModelStage<>(executor);
+        stage.complete(value);
+        return stage;
+    }
+
+    static <U> ModelStage<U> asyncExceptionally(Exception e, Executor executor)
+    {
+        ModelStage<U> stage = new AsyncModelStage<>(executor);
+        stage.completeExceptionally(e);
+        return stage;
+    }
+
+    @Override
+    public CompletionStage<WatchedEvent> event()
+    {
+        return event;
+    }
+
+    private ModelStage(CompletionStage<WatchedEvent> event)
+    {
+        this.event = event;
+    }
+
+    private static class AsyncModelStage<U> extends ModelStage<U>
+    {
+        private final Executor executor;
+
+        public AsyncModelStage(Executor executor)
+        {
+            super(null);
+            this.executor = executor;
+        }
+
+        @Override
+        public <U1> CompletableFuture<U1> thenApplyAsync(Function<? super U, ? extends U1> fn)
+        {
+            return super.thenApplyAsync(fn, executor);
+        }
+
+        @Override
+        public CompletableFuture<Void> thenAcceptAsync(Consumer<? super U> action)
+        {
+            return super.thenAcceptAsync(action, executor);
+        }
+
+        @Override
+        public CompletableFuture<Void> thenRunAsync(Runnable action)
+        {
+            return super.thenRunAsync(action, executor);
+        }
+
+        @Override
+        public <U1, V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U1> other, BiFunction<? super U, ? super U1, ? extends V> fn)
+        {
+            return super.thenCombineAsync(other, fn, executor);
+        }
+
+        @Override
+        public <U1> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U1> other, BiConsumer<? super U, ? super U1> action)
+        {
+            return super.thenAcceptBothAsync(other, action, executor);
+        }
+
+        @Override
+        public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other, Runnable action)
+        {
+            return super.runAfterBothAsync(other, action, executor);
+        }
+
+        @Override
+        public <U1> CompletableFuture<U1> applyToEitherAsync(CompletionStage<? extends U> other, Function<? super U, U1> fn)
+        {
+            return super.applyToEitherAsync(other, fn, executor);
+        }
+
+        @Override
+        public CompletableFuture<Void> acceptEitherAsync(CompletionStage<? extends U> other, Consumer<? super U> action)
+        {
+            return super.acceptEitherAsync(other, action, executor);
+        }
+
+        @Override
+        public CompletableFuture<Void> runAfterEitherAsync(CompletionStage<?> other, Runnable action)
+        {
+            return super.runAfterEitherAsync(other, action, executor);
+        }
+
+        @Override
+        public <U1> CompletableFuture<U1> thenComposeAsync(Function<? super U, ? extends CompletionStage<U1>> fn)
+        {
+            return super.thenComposeAsync(fn, executor);
+        }
+
+        @Override
+        public CompletableFuture<U> whenCompleteAsync(BiConsumer<? super U, ? super Throwable> action)
+        {
+            return super.whenCompleteAsync(action, executor);
+        }
+
+        @Override
+        public <U1> CompletableFuture<U1> handleAsync(BiFunction<? super U, Throwable, ? extends U1> fn)
+        {
+            return super.handleAsync(fn, executor);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledCacheImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledCacheImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledCacheImpl.java
new file mode 100644
index 0000000..72e6762
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledCacheImpl.java
@@ -0,0 +1,211 @@
+/**
+ * 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.details;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.listen.Listenable;
+import org.apache.curator.framework.listen.ListenerContainer;
+import org.apache.curator.framework.recipes.cache.TreeCache;
+import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
+import org.apache.curator.framework.recipes.cache.TreeCacheListener;
+import org.apache.curator.utils.ThreadUtils;
+import org.apache.curator.x.async.api.CreateOption;
+import org.apache.curator.x.async.modeled.ModelSerializer;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.curator.x.async.modeled.cached.ModeledCache;
+import org.apache.curator.x.async.modeled.cached.ModeledCacheListener;
+import org.apache.curator.x.async.modeled.ZNode;
+import org.apache.zookeeper.data.Stat;
+import java.util.AbstractMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+
+class ModeledCacheImpl<T> implements TreeCacheListener, ModeledCache<T>
+{
+    private final TreeCache cache;
+    private final Map<ZPath, Entry<T>> entries = new ConcurrentHashMap<>();
+    private final ModelSerializer<T> serializer;
+    private final ListenerContainer<ModeledCacheListener<T>> listenerContainer = new ListenerContainer<>();
+    private final ZPath basePath;
+
+    private static final class Entry<T>
+    {
+        final Stat stat;
+        final T model;
+
+        Entry(Stat stat, T model)
+        {
+            this.stat = stat;
+            this.model = model;
+        }
+    }
+
+    ModeledCacheImpl(CuratorFramework client, ModelSpec<T> modelSpec, ExecutorService executor)
+    {
+        if ( !modelSpec.path().isResolved() && !modelSpec.path().isRoot() && modelSpec.path().parent().isResolved() )
+        {
+            modelSpec = modelSpec.parent(); // i.e. the last item is a parameter
+        }
+
+        basePath = modelSpec.path();
+        this.serializer = modelSpec.serializer();
+        cache = TreeCache.newBuilder(client, basePath.fullPath())
+            .setCacheData(false)
+            .setDataIsCompressed(modelSpec.createOptions().contains(CreateOption.compress))
+            .setExecutor(executor)
+            .setCreateParentNodes(modelSpec.createOptions().contains(CreateOption.createParentsIfNeeded) || modelSpec.createOptions().contains(CreateOption.createParentsAsContainers))
+            .build();
+    }
+
+    public void start()
+    {
+        try
+        {
+            cache.getListenable().addListener(this);
+            cache.start();
+        }
+        catch ( Exception e )
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void close()
+    {
+        cache.getListenable().removeListener(this);
+        cache.close();
+        entries.clear();
+    }
+
+    @Override
+    public Optional<ZNode<T>> currentData(ZPath path)
+    {
+        Entry<T> entry = entries.remove(path);
+        if ( entry != null )
+        {
+            return Optional.of(new ZNodeImpl<>(path, entry.stat, entry.model));
+        }
+        return Optional.empty();
+    }
+
+    ZPath basePath()
+    {
+        return basePath;
+    }
+
+    Map<ZPath, ZNode<T>> currentChildren()
+    {
+        return currentChildren(basePath);
+    }
+
+    @Override
+    public Map<ZPath, ZNode<T>> currentChildren(ZPath path)
+    {
+        return entries.entrySet()
+            .stream()
+            .filter(entry -> entry.getKey().startsWith(path))
+            .map(entry -> new AbstractMap.SimpleEntry<>(entry.getKey(), new ZNodeImpl<>(entry.getKey(), entry.getValue().stat, entry.getValue().model)))
+            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+    }
+
+    public Listenable<ModeledCacheListener<T>> listenable()
+    {
+        return listenerContainer;
+    }
+
+    @Override
+    public void childEvent(CuratorFramework client, TreeCacheEvent event)
+    {
+        try
+        {
+            internalChildEvent(event);
+        }
+        catch ( Exception e )
+        {
+            ThreadUtils.checkInterrupted(e);
+
+            listenerContainer.forEach(l -> {
+                l.handleException(e);
+                return null;
+            });
+        }
+    }
+
+    private void internalChildEvent(TreeCacheEvent event) throws Exception
+    {
+        switch ( event.getType() )
+        {
+        case NODE_ADDED:
+        case NODE_UPDATED:
+        {
+            ZPath path = ZPath.parse(event.getData().getPath());
+            if ( !path.equals(basePath) )
+            {
+                byte[] bytes = event.getData().getData();
+                if ( (bytes != null) && (bytes.length > 0) )    // otherwise it's probably just a parent node being created
+                {
+                    T model = serializer.deserialize(bytes);
+                    entries.put(path, new Entry<>(event.getData().getStat(), model));
+                    ModeledCacheListener.Type type = (event.getType() == TreeCacheEvent.Type.NODE_ADDED) ? ModeledCacheListener.Type.NODE_ADDED : ModeledCacheListener.Type.NODE_UPDATED;
+                    accept(type, path, event.getData().getStat(), model);
+                }
+            }
+            break;
+        }
+
+        case NODE_REMOVED:
+        {
+            ZPath path = ZPath.parse(event.getData().getPath());
+            if ( !path.equals(basePath) )
+            {
+                Entry<T> entry = entries.remove(path);
+                T model = (entry != null) ? entry.model : serializer.deserialize(event.getData().getData());
+                Stat stat = (entry != null) ? entry.stat : event.getData().getStat();
+                accept(ModeledCacheListener.Type.NODE_REMOVED, path, stat, model);
+            }
+            break;
+        }
+
+        case INITIALIZED:
+        {
+            listenerContainer.forEach(l -> {
+                l.initialized();
+                return null;
+            });
+            break;
+        }
+
+        default:
+            // ignore
+            break;
+        }
+    }
+
+    private void accept(ModeledCacheListener.Type type, ZPath path, Stat stat, T model)
+    {
+        listenerContainer.forEach(l -> {
+            l.accept(type, path, stat, model);
+            return null;
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java
new file mode 100644
index 0000000..c1d19c4
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java
@@ -0,0 +1,450 @@
+/**
+ * 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.details;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import org.apache.curator.framework.api.CuratorEvent;
+import org.apache.curator.framework.api.UnhandledErrorListener;
+import org.apache.curator.framework.api.transaction.CuratorOp;
+import org.apache.curator.framework.api.transaction.CuratorTransactionResult;
+import org.apache.curator.utils.ThreadUtils;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.curator.x.async.WatchMode;
+import org.apache.curator.x.async.api.AsyncCuratorFrameworkDsl;
+import org.apache.curator.x.async.api.AsyncPathAndBytesable;
+import org.apache.curator.x.async.api.AsyncPathable;
+import org.apache.curator.x.async.api.AsyncTransactionSetDataBuilder;
+import org.apache.curator.x.async.api.CreateOption;
+import org.apache.curator.x.async.api.WatchableAsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ZNode;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.curator.x.async.modeled.cached.CachedModeledFramework;
+import org.apache.curator.x.async.modeled.versioned.VersionedModeledFramework;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Stat;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Function;
+import java.util.function.UnaryOperator;
+import java.util.stream.Collectors;
+
+public class ModeledFrameworkImpl<T> implements ModeledFramework<T>
+{
+    private final AsyncCuratorFramework client;
+    private final WatchableAsyncCuratorFramework watchableClient;
+    private final ModelSpec<T> modelSpec;
+    private final WatchMode watchMode;
+    private final UnaryOperator<WatchedEvent> watcherFilter;
+    private final UnhandledErrorListener unhandledErrorListener;
+    private final UnaryOperator<CuratorEvent> resultFilter;
+    private final AsyncCuratorFrameworkDsl dslClient;
+    private final boolean isWatched;
+
+    public static <T> ModeledFrameworkImpl<T> build(AsyncCuratorFramework client, ModelSpec<T> model, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter)
+    {
+        boolean isWatched = (watchMode != null);
+
+        Objects.requireNonNull(client, "client cannot be null");
+        Objects.requireNonNull(model, "model cannot be null");
+
+        watchMode = (watchMode != null) ? watchMode : WatchMode.stateChangeAndSuccess;
+
+        AsyncCuratorFrameworkDsl dslClient = client.with(watchMode, unhandledErrorListener, resultFilter, watcherFilter);
+        WatchableAsyncCuratorFramework watchableClient = isWatched ? dslClient.watched() : dslClient;
+
+        return new ModeledFrameworkImpl<>(
+            client,
+            dslClient,
+            watchableClient,
+            model,
+            watchMode,
+            watcherFilter,
+            unhandledErrorListener,
+            resultFilter,
+            isWatched
+        );
+    }
+
+    private ModeledFrameworkImpl(AsyncCuratorFramework client, AsyncCuratorFrameworkDsl dslClient, WatchableAsyncCuratorFramework watchableClient, ModelSpec<T> modelSpec, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter, boolean isWatched)
+    {
+        this.client = client;
+        this.dslClient = dslClient;
+        this.watchableClient = watchableClient;
+        this.modelSpec = modelSpec;
+        this.watchMode = watchMode;
+        this.watcherFilter = watcherFilter;
+        this.unhandledErrorListener = unhandledErrorListener;
+        this.resultFilter = resultFilter;
+        this.isWatched = isWatched;
+    }
+
+    @Override
+    public CachedModeledFramework<T> cached()
+    {
+        return cached(ThreadUtils.newSingleThreadExecutor("CachedModeledFramework"));
+    }
+
+    @Override
+    public CachedModeledFramework<T> cached(ExecutorService executor)
+    {
+        Preconditions.checkState(!isWatched, "CachedModeledFramework cannot be used with watched instances as the internal cache would bypass the watchers.");
+        return new CachedModeledFrameworkImpl<>(this, Objects.requireNonNull(executor, "executor cannot be null"));
+    }
+
+    @Override
+    public VersionedModeledFramework<T> versioned()
+    {
+        return new VersionedModeledFrameworkImpl<>(this);
+    }
+
+    @Override
+    public ModelSpec<T> modelSpec()
+    {
+        return modelSpec;
+    }
+
+    @Override
+    public AsyncCuratorFramework unwrap()
+    {
+        return client;
+    }
+
+    @Override
+    public AsyncStage<String> set(T item)
+    {
+        return set(item, null, -1);
+    }
+
+    @Override
+    public AsyncStage<String> set(T item, Stat storingStatIn)
+    {
+        return set(item, storingStatIn, -1);
+    }
+
+    @Override
+    public AsyncStage<String> set(T item, int version)
+    {
+        return set(item, null, -1);
+    }
+
+    @Override
+    public AsyncStage<String> set(T item, Stat storingStatIn, int version)
+    {
+        try
+        {
+            byte[] bytes = modelSpec.serializer().serialize(item);
+            return dslClient.create()
+                .withOptions(modelSpec.createOptions(), modelSpec.createMode(), fixAclList(modelSpec.aclList()), storingStatIn, modelSpec.ttl(), version)
+                .forPath(resolveForSet(item), bytes);
+        }
+        catch ( Exception e )
+        {
+            return ModelStage.exceptionally(e);
+        }
+    }
+
+    @Override
+    public AsyncStage<T> read()
+    {
+        return internalRead(ZNode::model, null);
+    }
+
+    @Override
+    public AsyncStage<T> read(Stat storingStatIn)
+    {
+        return internalRead(ZNode::model, storingStatIn);
+    }
+
+    @Override
+    public AsyncStage<ZNode<T>> readAsZNode()
+    {
+        return internalRead(Function.identity(), null);
+    }
+
+    @Override
+    public AsyncStage<Stat> update(T item)
+    {
+        return update(item, -1);
+    }
+
+    @Override
+    public AsyncStage<Stat> update(T item, int version)
+    {
+        try
+        {
+            byte[] bytes = modelSpec.serializer().serialize(item);
+            AsyncPathAndBytesable<AsyncStage<Stat>> next = isCompressed() ? dslClient.setData().compressedWithVersion(version) : dslClient.setData();
+            return next.forPath(resolveForSet(item), bytes);
+        }
+        catch ( Exception e )
+        {
+            return ModelStage.exceptionally(e);
+        }
+    }
+
+    @Override
+    public AsyncStage<Stat> checkExists()
+    {
+        return watchableClient.checkExists().forPath(modelSpec.path().fullPath());
+    }
+
+    @Override
+    public AsyncStage<Void> delete()
+    {
+        return delete(-1);
+    }
+
+    @Override
+    public AsyncStage<Void> delete(int version)
+    {
+        return dslClient.delete().withVersion(-1).forPath(modelSpec.path().fullPath());
+    }
+
+    @Override
+    public AsyncStage<List<ZPath>> children()
+    {
+        return internalGetChildren(modelSpec.path());
+    }
+
+    @Override
+    public AsyncStage<List<ZNode<T>>> childrenAsZNodes()
+    {
+        ModelStage<List<ZNode<T>>> modelStage = ModelStage.make();
+        Preconditions.checkState(!isWatched, "childrenAsZNodes() cannot be used with watched instances.");
+        children().handle((children, e) -> {
+            if ( e != null )
+            {
+                modelStage.completeExceptionally(e);
+            }
+            else
+            {
+                completeChildrenAsZNodes(modelStage, children);
+            }
+            return null;
+        });
+        return modelStage;
+    }
+
+    private void completeChildrenAsZNodes(ModelStage<List<ZNode<T>>> modelStage, List<ZPath> children)
+    {
+        List<ZNode<T>> nodes = Lists.newArrayList();
+        if ( children.size() == 0 )
+        {
+            modelStage.complete(nodes);
+            return;
+        }
+        children.forEach(path -> withPath(path).readAsZNode().handle((node, e) -> {
+            if ( e != null )
+            {
+                modelStage.completeExceptionally(e);
+            }
+            else
+            {
+                nodes.add(node);
+                if ( nodes.size() == children.size() )
+                {
+                    modelStage.complete(nodes);
+                }
+            }
+            return null;
+        }));
+    }
+
+    private AsyncStage<List<ZPath>> internalGetChildren(ZPath path)
+    {
+        AsyncStage<List<String>> asyncStage = watchableClient.getChildren().forPath(path.fullPath());
+        ModelStage<List<ZPath>> modelStage = ModelStage.make(asyncStage.event());
+        asyncStage.whenComplete((children, e) -> {
+            if ( e != null )
+            {
+                modelStage.completeExceptionally(e);
+            }
+            else
+            {
+                modelStage.complete(children.stream().map(path::child).collect(Collectors.toList()));
+            }
+        });
+        return modelStage;
+    }
+
+    @Override
+    public ModeledFramework<T> parent()
+    {
+        ModelSpec<T> newModelSpec = modelSpec.parent();
+        return new ModeledFrameworkImpl<>(
+            client,
+            dslClient,
+            watchableClient,
+            newModelSpec,
+            watchMode,
+            watcherFilter,
+            unhandledErrorListener,
+            resultFilter,
+            isWatched
+        );
+    }
+
+    @Override
+    public ModeledFramework<T> child(Object child)
+    {
+        ModelSpec<T> newModelSpec = modelSpec.child(child);
+        return new ModeledFrameworkImpl<>(
+            client,
+            dslClient,
+            watchableClient,
+            newModelSpec,
+            watchMode,
+            watcherFilter,
+            unhandledErrorListener,
+            resultFilter,
+            isWatched
+        );
+    }
+
+    @Override
+    public ModeledFramework<T> withPath(ZPath path)
+    {
+        ModelSpec<T> newModelSpec = modelSpec.withPath(path);
+        return new ModeledFrameworkImpl<>(
+            client,
+            dslClient,
+            watchableClient,
+            newModelSpec,
+            watchMode,
+            watcherFilter,
+            unhandledErrorListener,
+            resultFilter,
+            isWatched
+        );
+    }
+
+    public static boolean isCompressed(Set<CreateOption> createOptions)
+    {
+        return createOptions.contains(CreateOption.compress);
+    }
+
+    @Override
+    public CuratorOp createOp(T model)
+    {
+        return client.transactionOp()
+            .create()
+            .withOptions(modelSpec.createMode(), fixAclList(modelSpec.aclList()), modelSpec.createOptions().contains(CreateOption.compress), modelSpec.ttl())
+            .forPath(resolveForSet(model), modelSpec.serializer().serialize(model));
+    }
+
+    @Override
+    public CuratorOp updateOp(T model)
+    {
+        return updateOp(model, -1);
+    }
+
+    @Override
+    public CuratorOp updateOp(T model, int version)
+    {
+        AsyncTransactionSetDataBuilder builder = client.transactionOp().setData();
+        if ( isCompressed() )
+        {
+            return builder.withVersionCompressed(version).forPath(resolveForSet(model), modelSpec.serializer().serialize(model));
+        }
+        return builder.withVersion(version).forPath(resolveForSet(model), modelSpec.serializer().serialize(model));
+    }
+
+    @Override
+    public CuratorOp deleteOp()
+    {
+        return deleteOp(-1);
+    }
+
+    @Override
+    public CuratorOp deleteOp(int version)
+    {
+        return client.transactionOp().delete().withVersion(version).forPath(modelSpec.path().fullPath());
+    }
+
+    @Override
+    public CuratorOp checkExistsOp()
+    {
+        return checkExistsOp(-1);
+    }
+
+    @Override
+    public CuratorOp checkExistsOp(int version)
+    {
+        return client.transactionOp().check().withVersion(version).forPath(modelSpec.path().fullPath());
+    }
+
+    @Override
+    public AsyncStage<List<CuratorTransactionResult>> inTransaction(List<CuratorOp> operations)
+    {
+        return client.transaction().forOperations(operations);
+    }
+
+    private boolean isCompressed()
+    {
+        return modelSpec.createOptions().contains(CreateOption.compress);
+    }
+
+    private <U> ModelStage<U> internalRead(Function<ZNode<T>, U> resolver, Stat storingStatIn)
+    {
+        Stat stat = (storingStatIn != null) ? storingStatIn : new Stat();
+        AsyncPathable<AsyncStage<byte[]>> next = isCompressed() ? watchableClient.getData().decompressedStoringStatIn(stat) : watchableClient.getData().storingStatIn(stat);
+        AsyncStage<byte[]> asyncStage = next.forPath(modelSpec.path().fullPath());
+        ModelStage<U> modelStage = ModelStage.make(asyncStage.event());
+        asyncStage.whenComplete((value, e) -> {
+            if ( e != null )
+            {
+                modelStage.completeExceptionally(e);
+            }
+            else
+            {
+                try
+                {
+                    ZNode<T> node = new ZNodeImpl<>(modelSpec.path(), stat, modelSpec.serializer().deserialize(value));
+                    modelStage.complete(resolver.apply(node));
+                }
+                catch ( Exception deserializeException )
+                {
+                    modelStage.completeExceptionally(deserializeException);
+                }
+            }
+        });
+        return modelStage;
+    }
+
+    private String resolveForSet(T model)
+    {
+        if ( modelSpec.path().isResolved() )
+        {
+            return modelSpec.path().fullPath();
+        }
+        return modelSpec.path().resolved(model).fullPath();
+    }
+
+    private List<ACL> fixAclList(List<ACL> aclList)
+    {
+        return (aclList.size() > 0) ? aclList : null;   // workaround for old, bad design. empty list not accepted
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/VersionedModeledFrameworkImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/VersionedModeledFrameworkImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/VersionedModeledFrameworkImpl.java
new file mode 100644
index 0000000..89d7615
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/VersionedModeledFrameworkImpl.java
@@ -0,0 +1,85 @@
+/**
+ * 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.details;
+
+import org.apache.curator.framework.api.transaction.CuratorOp;
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.versioned.Versioned;
+import org.apache.curator.x.async.modeled.versioned.VersionedModeledFramework;
+import org.apache.zookeeper.data.Stat;
+
+class VersionedModeledFrameworkImpl<T> implements VersionedModeledFramework<T>
+{
+    private final ModeledFramework<T> client;
+
+    VersionedModeledFrameworkImpl(ModeledFramework<T> client)
+    {
+        this.client = client;
+    }
+
+    @Override
+    public AsyncStage<String> set(Versioned<T> model)
+    {
+        return client.set(model.model(), model.version());
+    }
+
+    @Override
+    public AsyncStage<String> set(Versioned<T> model, Stat storingStatIn)
+    {
+        return client.set(model.model(), storingStatIn, model.version());
+    }
+
+    @Override
+    public AsyncStage<Versioned<T>> read()
+    {
+        return read(null);
+    }
+
+    @Override
+    public AsyncStage<Versioned<T>> read(Stat storingStatIn)
+    {
+        Stat localStat = (storingStatIn != null) ? storingStatIn : new Stat();
+        AsyncStage<T> stage = client.read(localStat);
+        ModelStage<Versioned<T>> modelStage = ModelStage.make(stage.event());
+        stage.whenComplete((model, e) -> {
+           if ( e != null )
+           {
+               modelStage.completeExceptionally(e);
+           }
+           else
+           {
+               modelStage.complete(Versioned.from(model, localStat.getVersion()));
+           }
+        });
+        return modelStage;
+    }
+
+    @Override
+    public AsyncStage<Stat> update(Versioned<T> model)
+    {
+        return client.update(model.model(), model.version());
+    }
+
+    @Override
+    public CuratorOp updateOp(Versioned<T> model)
+    {
+        return client.updateOp(model.model(), model.version());
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZNodeImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZNodeImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZNodeImpl.java
new file mode 100644
index 0000000..85bedf4
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZNodeImpl.java
@@ -0,0 +1,56 @@
+/**
+ * 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.details;
+
+import org.apache.curator.x.async.modeled.ZNode;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.zookeeper.data.Stat;
+import java.util.Objects;
+
+public class ZNodeImpl<T> implements ZNode<T>
+{
+    private final ZPath path;
+    private final Stat stat;
+    private final T model;
+
+    public ZNodeImpl(ZPath path, Stat stat, T model)
+    {
+        this.path = Objects.requireNonNull(path, "path cannot be null");
+        this.stat = Objects.requireNonNull(stat, "stat cannot be null");
+        this.model = Objects.requireNonNull(model, "model cannot be null");
+    }
+
+    @Override
+    public ZPath path()
+    {
+        return path;
+    }
+
+    @Override
+    public Stat stat()
+    {
+        return stat;
+    }
+
+    @Override
+    public T model()
+    {
+        return model;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZPathImpl.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZPathImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZPathImpl.java
new file mode 100644
index 0000000..fff742e
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ZPathImpl.java
@@ -0,0 +1,289 @@
+/**
+ * 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.details;
+
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableList;
+import org.apache.curator.x.async.modeled.NodeName;
+import org.apache.curator.x.async.modeled.ZPath;
+import org.apache.zookeeper.common.PathUtils;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.function.UnaryOperator;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import static org.apache.curator.utils.ZKPaths.PATH_SEPARATOR;
+
+public class ZPathImpl implements ZPath
+{
+    public static final ZPath root = new ZPathImpl(Collections.singletonList(PATH_SEPARATOR), null);
+
+    private final List<String> nodes;
+    private final boolean isResolved;
+    private volatile String fullPath = null;
+    private volatile ZPath parent = null;
+    private volatile Pattern schema = null;
+
+    public static ZPath parse(String fullPath, UnaryOperator<String> nameFilter)
+    {
+        return parseInternal(fullPath, nameFilter);
+    }
+
+    private static ZPathImpl parseInternal(String fullPath, UnaryOperator<String> nameFilter)
+    {
+        List<String> nodes = ImmutableList.<String>builder()
+            .add(PATH_SEPARATOR)
+            .addAll(
+                Splitter.on(PATH_SEPARATOR)
+                    .omitEmptyStrings()
+                    .splitToList(fullPath)
+                    .stream()
+                    .map(nameFilter)
+                    .collect(Collectors.toList())
+             )
+            .build();
+        nodes.forEach(ZPathImpl::validate);
+        return new ZPathImpl(nodes, null);
+    }
+
+    public static ZPath from(String[] names)
+    {
+        return from(null, Arrays.asList(names));
+    }
+
+    public static ZPath from(List<String> names)
+    {
+        return from(null, names);
+    }
+
+    public static ZPath from(ZPath base, String[] names)
+    {
+        return from(base, Arrays.asList(names));
+    }
+
+    public static ZPath from(ZPath base, List<String> names)
+    {
+        names = Objects.requireNonNull(names, "names cannot be null");
+        names.forEach(ZPathImpl::validate);
+        ImmutableList.Builder<String> builder = ImmutableList.builder();
+        if ( base != null )
+        {
+            if ( base instanceof ZPathImpl )
+            {
+                builder.addAll(((ZPathImpl)base).nodes);
+            }
+            else
+            {
+                builder.addAll(Splitter.on(PATH_SEPARATOR).omitEmptyStrings().splitToList(base.fullPath()));
+            }
+        }
+        else
+        {
+            builder.add(PATH_SEPARATOR);
+        }
+        List<String> nodes = builder.addAll(names).build();
+        return new ZPathImpl(nodes, null);
+    }
+
+    @Override
+    public ZPath child(Object child)
+    {
+        return new ZPathImpl(nodes, NodeName.nameFrom(child));
+    }
+
+    @Override
+    public ZPath parent()
+    {
+        checkRootAccess();
+        if ( parent == null )
+        {
+            parent = new ZPathImpl(nodes.subList(0, nodes.size() - 1), null);
+        }
+        return parent;
+    }
+
+    @Override
+    public boolean isRoot()
+    {
+        return nodes.size() == 1;
+    }
+
+    @Override
+    public boolean startsWith(ZPath path)
+    {
+        ZPathImpl rhs;
+        if ( path instanceof ZPathImpl )
+        {
+            rhs = (ZPathImpl)path;
+        }
+        else
+        {
+            rhs = parseInternal(path.fullPath(), s -> s);
+        }
+        return (nodes.size() >= rhs.nodes.size()) && nodes.subList(0, rhs.nodes.size()).equals(rhs.nodes);
+    }
+
+    @Override
+    public Pattern toSchemaPathPattern()
+    {
+        if ( schema == null )
+        {
+            schema = Pattern.compile(buildFullPath(s -> isParameter(s) ? ".*" : s));
+        }
+        return schema;
+    }
+
+    @Override
+    public String fullPath()
+    {
+        checkResolved();
+        if ( fullPath == null )
+        {
+            fullPath = buildFullPath(s -> s);
+        }
+        return fullPath;
+    }
+
+    @Override
+    public String nodeName()
+    {
+        return nodes.get(nodes.size() - 1);
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        ZPathImpl zPaths = (ZPathImpl)o;
+
+        return nodes.equals(zPaths.nodes);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return nodes.hashCode();
+    }
+
+    @Override
+    public String toString()
+    {
+        return nodes.subList(1, nodes.size())
+            .stream().map(name -> isParameter(name) ? name.substring(1) : name)
+            .collect(Collectors.joining(PATH_SEPARATOR, PATH_SEPARATOR, ""));
+    }
+
+    @Override
+    public ZPath resolved(List<Object> parameters)
+    {
+        Iterator<Object> iterator = parameters.iterator();
+        List<String> nodeNames = nodes.stream()
+            .map(name -> {
+                if ( isParameter(name) && iterator.hasNext() )
+                {
+                    return NodeName.nameFrom(iterator.next());
+                }
+                return name;
+            })
+            .collect(Collectors.toList());
+        return new ZPathImpl(nodeNames, null);
+    }
+
+    @Override
+    public boolean isResolved()
+    {
+        return isResolved;
+    }
+
+    private static boolean isParameter(String name)
+    {
+        return (name.length() > 1) && name.startsWith(PATH_SEPARATOR);
+    }
+
+    private ZPathImpl(List<String> nodes, String child)
+    {
+        ImmutableList.Builder<String> builder = ImmutableList.<String>builder().addAll(nodes);
+        if ( child != null )
+        {
+            validate(child);
+            builder.add(child);
+        }
+        this.nodes = builder.build();
+        isResolved = this.nodes.stream().noneMatch(ZPathImpl::isParameter);
+    }
+
+    private void checkRootAccess()
+    {
+        if ( isRoot() )
+        {
+            throw new NoSuchElementException("The root has no parent");
+        }
+    }
+
+    private void checkResolved()
+    {
+        if ( !isResolved)
+        {
+            throw new IllegalStateException("This ZPath has not been resolved: " + toString());
+        }
+    }
+
+    private static void validate(String nodeName)
+    {
+        if ( isParameter(Objects.requireNonNull(nodeName, "nodeName cannot be null")) )
+        {
+            return;
+        }
+        if ( nodeName.equals(PATH_SEPARATOR) )
+        {
+            return;
+        }
+        PathUtils.validatePath(PATH_SEPARATOR + nodeName);
+    }
+
+    private String buildFullPath(UnaryOperator<String> filter)
+    {
+        boolean addSeparator = false;
+        StringBuilder str = new StringBuilder();
+        int size = nodes.size();
+        int parameterIndex = 0;
+        for ( int i = 0; i < size; ++i )
+        {
+            if ( i > 1 )
+            {
+                str.append(PATH_SEPARATOR);
+            }
+            str.append(filter.apply(nodes.get(i)));
+        }
+        return str.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec.java
new file mode 100644
index 0000000..3fa9831
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec.java
@@ -0,0 +1,87 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * <p>
+ *     Abstraction that allows the construction of ModelSpecs using strongly typed parameter replacements.
+ *     For example, given a ModelSpec with a path such as "/root/registry/people/{id}" where "id" should
+ *     be <code>PersonId</code>.
+ * </p>
+ *
+ * <p>
+ * <pre><code>
+ * // Step 1. Create a typed ZPath
+ * TypedZPath&lt;PersonId&gt; typedPath = TypedZPath.from("/root/registry/people/{id}");
+ *
+ * // Step 2. Create a ModelSpec builder (do not build at this point)
+ * ModelSpecBuilder&lt;Person&gt; builder = ModelSpec.builder(JacksonModelSerializer.build(Person.class))
+ *
+ * // Step 3. Create a typed ModelSpec using the typed ZPath and ModelSpec builder
+ * TypedModelSpec&lt;Person, PersonId&gt; typedModelSpec = TypedModelSpec.from(builder, path);
+ *
+ * // later on the TypedModelSpec can be resolved into a useable ModelSpec
+ * ModelSpec&lt;Person&gt; modelSpec = typedModelSpec.resolve(personId);
+ * </pre></code>
+ * </p>
+ */
+@FunctionalInterface
+public interface TypedModelSpec<M, P1>
+{
+    /**
+     * Resolve into a ZPath using the given parameter
+     *
+     * @param p1 the parameter
+     * @return ZPath
+     */
+    ModelSpec<M> resolved(P1 p1);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1> TypedModelSpec<M, P1> from(ModelSpecBuilder<M> builder, TypedZPath<P1> path)
+    {
+        return p1 -> builder.withPath(path.resolved(p1)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1> TypedModelSpec<M, P1> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath<P1> zPath = TypedZPath.from(pathWithIds);
+        return p1 -> builder.withPath(zPath.resolved(p1)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec0.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec0.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec0.java
new file mode 100644
index 0000000..dee3506
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec0.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link TypedModelSpec}, but with 0 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec0<M>
+{
+    ModelSpec<M> resolved();
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved()} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M> TypedModelSpec0<M> from(ModelSpecBuilder<M> builder, TypedZPath0 path)
+    {
+        return () -> builder.withPath(path.resolved()).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved()} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M> TypedModelSpec0<M> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath0 zPath = TypedZPath0.from(pathWithIds);
+        return () -> builder.withPath(zPath.resolved()).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec10.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec10.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec10.java
new file mode 100644
index 0000000..1b00d66
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec10.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 10 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> TypedModelSpec10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> from(ModelSpecBuilder<M> builder, TypedZPath10<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) -> builder.withPath(path.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> TypedModelSpec10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath10<P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> zPath = TypedZPath10.from(pathWithIds);
+        return (p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) -> builder.withPath(zPath.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec2.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec2.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec2.java
new file mode 100644
index 0000000..a56e139
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec2.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 2 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec2<M, P1, P2>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2> TypedModelSpec2<M, P1, P2> from(ModelSpecBuilder<M> builder, TypedZPath2<P1, P2> path)
+    {
+        return (p1, p2) -> builder.withPath(path.resolved(p1, p2)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2> TypedModelSpec2<M, P1, P2> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath2<P1, P2> zPath = TypedZPath2.from(pathWithIds);
+        return (p1, p2) -> builder.withPath(zPath.resolved(p1, p2)).build();
+    }
+}


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

Posted by ra...@apache.org.
http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec3.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec3.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec3.java
new file mode 100644
index 0000000..f4e808c
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec3.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 3 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec3<M, P1, P2, P3>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3> TypedModelSpec3<M, P1, P2, P3> from(ModelSpecBuilder<M> builder, TypedZPath3<P1, P2, P3> path)
+    {
+        return (p1, p2, p3) -> builder.withPath(path.resolved(p1, p2, p3)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3> TypedModelSpec3<M, P1, P2, P3> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath3<P1, P2, P3> zPath = TypedZPath3.from(pathWithIds);
+        return (p1, p2, p3) -> builder.withPath(zPath.resolved(p1, p2, p3)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec4.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec4.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec4.java
new file mode 100644
index 0000000..040f8a6
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec4.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 4 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec4<M, P1, P2, P3, P4>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3, P4 p4);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4> TypedModelSpec4<M, P1, P2, P3, P4> from(ModelSpecBuilder<M> builder, TypedZPath4<P1, P2, P3, P4> path)
+    {
+        return (p1, p2, p3, p4) -> builder.withPath(path.resolved(p1, p2, p3, p4)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4> TypedModelSpec4<M, P1, P2, P3, P4> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath4<P1, P2, P3, P4> zPath = TypedZPath4.from(pathWithIds);
+        return (p1, p2, p3, p4) -> builder.withPath(zPath.resolved(p1, p2, p3, p4)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec5.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec5.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec5.java
new file mode 100644
index 0000000..b67f5b0
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec5.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 5 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec5<M, P1, P2, P3, P4, P5>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5> TypedModelSpec5<M, P1, P2, P3, P4, P5> from(ModelSpecBuilder<M> builder, TypedZPath5<P1, P2, P3, P4, P5> path)
+    {
+        return (p1, p2, p3, p4, p5) -> builder.withPath(path.resolved(p1, p2, p3, p4, p5)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5> TypedModelSpec5<M, P1, P2, P3, P4, P5> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath5<P1, P2, P3, P4, P5> zPath = TypedZPath5.from(pathWithIds);
+        return (p1, p2, p3, p4, p5) -> builder.withPath(zPath.resolved(p1, p2, p3, p4, p5)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec6.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec6.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec6.java
new file mode 100644
index 0000000..675029a
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec6.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 6 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec6<M, P1, P2, P3, P4, P5, P6>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6> TypedModelSpec6<M, P1, P2, P3, P4, P5, P6> from(ModelSpecBuilder<M> builder, TypedZPath6<P1, P2, P3, P4, P5, P6> path)
+    {
+        return (p1, p2, p3, p4, p5, p6) -> builder.withPath(path.resolved(p1, p2, p3, p4, p5, p6)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6> TypedModelSpec6<M, P1, P2, P3, P4, P5, P6> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath6<P1, P2, P3, P4, P5, P6> zPath = TypedZPath6.from(pathWithIds);
+        return (p1, p2, p3, p4, p5, p6) -> builder.withPath(zPath.resolved(p1, p2, p3, p4, p5, p6)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec7.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec7.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec7.java
new file mode 100644
index 0000000..8ffd3c0
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec7.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 7 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec7<M, P1, P2, P3, P4, P5, P6, P7>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7> TypedModelSpec7<M, P1, P2, P3, P4, P5, P6, P7> from(ModelSpecBuilder<M> builder, TypedZPath7<P1, P2, P3, P4, P5, P6, P7> path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7) -> builder.withPath(path.resolved(p1, p2, p3, p4, p5, p6, p7)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7> TypedModelSpec7<M, P1, P2, P3, P4, P5, P6, P7> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath7<P1, P2, P3, P4, P5, P6, P7> zPath = TypedZPath7.from(pathWithIds);
+        return (p1, p2, p3, p4, p5, p6, p7) -> builder.withPath(zPath.resolved(p1, p2, p3, p4, p5, p6, p7)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec8.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec8.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec8.java
new file mode 100644
index 0000000..3a9acd4
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec8.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 8 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec8<M, P1, P2, P3, P4, P5, P6, P7, P8>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8> TypedModelSpec8<M, P1, P2, P3, P4, P5, P6, P7, P8> from(ModelSpecBuilder<M> builder, TypedZPath8<P1, P2, P3, P4, P5, P6, P7, P8> path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7, p8) -> builder.withPath(path.resolved(p1, p2, p3, p4, p5, p6, p7, p8)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8> TypedModelSpec8<M, P1, P2, P3, P4, P5, P6, P7, P8> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath8<P1, P2, P3, P4, P5, P6, P7, P8> zPath = TypedZPath8.from(pathWithIds);
+        return (p1, p2, p3, p4, p5, p6, p7, p8) -> builder.withPath(zPath.resolved(p1, p2, p3, p4, p5, p6, p7, p8)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec9.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec9.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec9.java
new file mode 100644
index 0000000..94ff8d7
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModelSpec9.java
@@ -0,0 +1,61 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModelSpec}, but with 9 parameters
+ */
+@FunctionalInterface
+public interface TypedModelSpec9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9>
+{
+    ModelSpec<M> resolved(P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9);
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and typed path. When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param path typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9> TypedModelSpec9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9> from(ModelSpecBuilder<M> builder, TypedZPath9<P1, P2, P3, P4, P5, P6, P7, P8, P9> path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7, p8, p9) -> builder.withPath(path.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9)).build();
+    }
+
+    /**
+     * Return a new TypedModelSpec using the given model spec builder and path. A TypedZPath
+     * is created from the given full path and When
+     * {@link #resolved(Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual model spec is generated with the
+     * resolved path
+     *
+     * @param builder model spec builder
+     * @param pathWithIds typed path
+     * @return new TypedModelSpec
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9> TypedModelSpec9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9> from(ModelSpecBuilder<M> builder, String pathWithIds)
+    {
+        TypedZPath9<P1, P2, P3, P4, P5, P6, P7, P8, P9> zPath = TypedZPath9.from(pathWithIds);
+        return (p1, p2, p3, p4, p5, p6, p7, p8, p9) -> builder.withPath(zPath.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework.java
new file mode 100644
index 0000000..56a8ebc
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework.java
@@ -0,0 +1,93 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * <p>
+ *     Abstraction that allows the construction of ModeledFrameworks using strongly typed parameter replacements.
+ *     For example, given a ModeledFramework with a ModelSpec that has a path such as
+ *     "/root/registry/people/{id}" where "id" should be <code>PersonId</code>.
+ * </p>
+ *
+ * <p>
+ * <pre><code>
+ * // Step 1. Create a typed ZPath
+ * TypedZPath&lt;PersonId&gt; typedPath = TypedZPath.from("/root/registry/people/{id}");
+ *
+ * // Step 2. Create a typed ModelSpec (see TypedModelSpec for details)
+ * TypedModelSpec&lt;Person, PersonId&gt; typedModelSpec = TypedModelSpec.from(builder, path);
+ *
+ * // Step 3. Create a ModeledFramework builder (do not build at this point)
+ * ModeledFrameworkBuilder&lt;Person&gt; builder = ModeledFramework.builder()... // add any other needed options
+ *
+ * // Step 4. Create a typed TypedModeledFramework using the typed ZPath, typed ModelSpec, and ModeledFramework builder
+ * TypedModeledFramework&lt;Person, PersonId&gt; clientSpec = TypedModeledFramework.from(builder, modelSpec);
+ *
+ * // later on the TypedModelSpec can be resolved into a useable ModeledFramework
+ * ModeledFramework&lt;Person&gt; client = clientSpec.resolve(personId);
+ * </pre></code>
+ * </p>
+ */
+@FunctionalInterface
+public interface TypedModeledFramework<M, P1>
+{
+    /**
+     * Resolve into a ModeledFramework using the given parameter
+     *
+     * @param client the curator instance to use
+     * @param p1 the parameter
+     * @return ZPath
+     */
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1> TypedModeledFramework<M, P1> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec<M, P1> modelSpec)
+    {
+        return (client, p1) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1> TypedModeledFramework<M, P1> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec<M, P1> typedModelSpec = TypedModelSpec.from(modelSpecBuilder, pathWithIds);
+        return (client, p1) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework0.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework0.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework0.java
new file mode 100644
index 0000000..8c9de04
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework0.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link TypedModeledFramework}, but with 0 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework0<M>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(org.apache.curator.x.async.AsyncCuratorFramework)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M> TypedModeledFramework0<M> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec0<M> modelSpec)
+    {
+        return (client) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved()).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(org.apache.curator.x.async.AsyncCuratorFramework)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M> TypedModeledFramework0<M> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec0<M> typedModelSpec = TypedModelSpec0.from(modelSpecBuilder, pathWithIds);
+        return (client) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved()).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework10.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework10.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework10.java
new file mode 100644
index 0000000..24a4671
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework10.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 10 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9, P10 p10);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> TypedModeledFramework10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> modelSpec)
+    {
+        return (client, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> TypedModeledFramework10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec10<M, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10> typedModelSpec = TypedModelSpec10.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework2.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework2.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework2.java
new file mode 100644
index 0000000..9a535e0
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework2.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 2 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework2<M, P1, P2>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2> TypedModeledFramework2<M, P1, P2> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec2<M, P1, P2> modelSpec)
+    {
+        return (client, p1, p2) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2> TypedModeledFramework2<M, P1, P2> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec2<M, P1, P2> typedModelSpec = TypedModelSpec2.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework3.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework3.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework3.java
new file mode 100644
index 0000000..6d6edc3
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework3.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 3 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework3<M, P1, P2, P3>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3> TypedModeledFramework3<M, P1, P2, P3> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec3<M, P1, P2, P3> modelSpec)
+    {
+        return (client, p1, p2, p3) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3> TypedModeledFramework3<M, P1, P2, P3> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec3<M, P1, P2, P3> typedModelSpec = TypedModelSpec3.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework4.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework4.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework4.java
new file mode 100644
index 0000000..ff88a0f
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework4.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 4 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework4<M, P1, P2, P3, P4>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3, P4 p4);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4> TypedModeledFramework4<M, P1, P2, P3, P4> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec4<M, P1, P2, P3, P4> modelSpec)
+    {
+        return (client, p1, p2, p3, p4) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3, p4)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4> TypedModeledFramework4<M, P1, P2, P3, P4> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec4<M, P1, P2, P3, P4> typedModelSpec = TypedModelSpec4.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3, p4) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3, p4)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework5.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework5.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework5.java
new file mode 100644
index 0000000..7579158
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework5.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 5 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework5<M, P1, P2, P3, P4, P5>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5> TypedModeledFramework5<M, P1, P2, P3, P4, P5> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec5<M, P1, P2, P3, P4, P5> modelSpec)
+    {
+        return (client, p1, p2, p3, p4, p5) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3, p4, p5)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5> TypedModeledFramework5<M, P1, P2, P3, P4, P5> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec5<M, P1, P2, P3, P4, P5> typedModelSpec = TypedModelSpec5.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3, p4, p5) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3, p4, p5)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework6.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework6.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework6.java
new file mode 100644
index 0000000..9b30a52
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework6.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 6 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework6<M, P1, P2, P3, P4, P5, P6>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6> TypedModeledFramework6<M, P1, P2, P3, P4, P5, P6> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec6<M, P1, P2, P3, P4, P5, P6> modelSpec)
+    {
+        return (client, p1, p2, p3, p4, p5, p6) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3, p4, p5, p6)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6> TypedModeledFramework6<M, P1, P2, P3, P4, P5, P6> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec6<M, P1, P2, P3, P4, P5, P6> typedModelSpec = TypedModelSpec6.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3, p4, p5, p6) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3, p4, p5, p6)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework7.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework7.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework7.java
new file mode 100644
index 0000000..f4947f2
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework7.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 7 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework7<M, P1, P2, P3, P4, P5, P6, P7>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7> TypedModeledFramework7<M, P1, P2, P3, P4, P5, P6, P7> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec7<M, P1, P2, P3, P4, P5, P6, P7> modelSpec)
+    {
+        return (client, p1, p2, p3, p4, p5, p6, p7) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3, p4, p5, p6, p7)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7> TypedModeledFramework7<M, P1, P2, P3, P4, P5, P6, P7> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec7<M, P1, P2, P3, P4, P5, P6, P7> typedModelSpec = TypedModelSpec7.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3, p4, p5, p6, p7) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3, p4, p5, p6, p7)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework8.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework8.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework8.java
new file mode 100644
index 0000000..2864df1
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework8.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 8 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework8<M, P1, P2, P3, P4, P5, P6, P7, P8>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8> TypedModeledFramework8<M, P1, P2, P3, P4, P5, P6, P7, P8> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec8<M, P1, P2, P3, P4, P5, P6, P7, P8> modelSpec)
+    {
+        return (client, p1, p2, p3, p4, p5, p6, p7, p8) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3, p4, p5, p6, p7, p8)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8> TypedModeledFramework8<M, P1, P2, P3, P4, P5, P6, P7, P8> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec8<M, P1, P2, P3, P4, P5, P6, P7, P8> typedModelSpec = TypedModelSpec8.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3, p4, p5, p6, p7, p8) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3, p4, p5, p6, p7, p8)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework9.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework9.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework9.java
new file mode 100644
index 0000000..6102943
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedModeledFramework9.java
@@ -0,0 +1,63 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ModeledFrameworkBuilder;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedModeledFramework}, but with 9 parameters
+ */
+@FunctionalInterface
+public interface TypedModeledFramework9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9>
+{
+    ModeledFramework<M> resolved(AsyncCuratorFramework client, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9);
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder and typed model spec.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpec TypedModelSpec
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9> TypedModeledFramework9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9> from(ModeledFrameworkBuilder<M> frameworkBuilder, TypedModelSpec9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9> modelSpec)
+    {
+        return (client, p1, p2, p3, p4, p5, p6, p7, p8, p9) -> frameworkBuilder.withClient(client).withModelSpec(modelSpec.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9)).build();
+    }
+
+    /**
+     * Return a new TypedModeledFramework using the given modeled framework builder, model spec builder and a path with ids.
+     * When {@link #resolved(AsyncCuratorFramework, Object, Object, Object, Object, Object, Object, Object, Object, Object)} is called the actual ModeledFramework is generated with the
+     * resolved model spec and resolved path
+     *
+     * @param frameworkBuilder ModeledFrameworkBuilder
+     * @param modelSpecBuilder model spec builder
+     * @param pathWithIds path with {XXXX} parameters
+     * @return new TypedModeledFramework
+     */
+    static <M, P1, P2, P3, P4, P5, P6, P7, P8, P9> TypedModeledFramework9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9> from(ModeledFrameworkBuilder<M> frameworkBuilder, ModelSpecBuilder<M> modelSpecBuilder, String pathWithIds)
+    {
+        TypedModelSpec9<M, P1, P2, P3, P4, P5, P6, P7, P8, P9> typedModelSpec = TypedModelSpec9.from(modelSpecBuilder, pathWithIds);
+        return (client, p1, p2, p3, p4, p5, p6, p7, p8, p9) -> frameworkBuilder.withClient(client).withModelSpec(typedModelSpec.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9)).build();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath.java
new file mode 100644
index 0000000..ed3dcd3
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath.java
@@ -0,0 +1,92 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * <p>
+ *     Abstraction that allows the construction of ZPaths using strongly typed parameter replacements.
+ *     For example, given a path such as "/root/registry/people/{id}" where "id" should be <code>PersonId</code>.
+ * </p>
+ *
+ * <p>
+ * <pre><code>
+ * TypedZPath&lt;PersonId&gt; typedPath = TypedZPath.from("/root/registry/people/{id}");
+ *
+ * ...
+ *
+ * ZPath path = typedPath.resolved(personId);
+ * </pre></code>
+ * </p>
+ *
+ * <p>
+ *     Additionally, if you have a model/class that implements {@link org.apache.curator.x.async.modeled.NodeName}
+ *     you can pass that when resolving. E.g.
+ * </p>
+ *
+ * <p>
+ * <pre><code>
+ * public class MyModel implements NodeName {
+ *     ...
+ *     public String nodeName() {
+ *         return modelId;
+ *     }
+ * }
+ *
+ * TypedZPath&lt;MyModel&gt; typedPath = TypedZPath.from("/foo/bar/{id}");
+ *
+ * MyModel model = ...
+ * ZPath path = typedPath.resolved(model);
+ * </pre></code>
+ * </p>
+ */
+@FunctionalInterface
+public interface TypedZPath<T>
+{
+    /**
+     * Resolve into a ZPath using the given parameter
+     *
+     * @param p1 the parameter
+     * @return ZPath
+     */
+    ZPath resolved(T p1);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T> TypedZPath<T> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T> TypedZPath<T> from(ZPath path)
+    {
+        return path::resolved;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath0.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath0.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath0.java
new file mode 100644
index 0000000..60576b2
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath0.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link TypedZPath}, but with 0 parameters.
+ */
+@FunctionalInterface
+public interface TypedZPath0
+{
+    ZPath resolved();
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static TypedZPath0 from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static TypedZPath0 from(ZPath path)
+    {
+        return path::resolved;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath10.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath10.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath10.java
new file mode 100644
index 0000000..b29f5ef
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath10.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 10 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7, T8 p8, T9 p9, T10 p10);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> TypedZPath10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> TypedZPath10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> from(ZPath path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7, p8, p9, p10) -> path.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath2.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath2.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath2.java
new file mode 100644
index 0000000..1feed76
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath2.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 2 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath2<T1, T2>
+{
+    ZPath resolved(T1 p1, T2 p2);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2> TypedZPath2<T1, T2> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2> TypedZPath2<T1, T2> from(ZPath path)
+    {
+        return (p1, p2) -> path.resolved(p1, p2);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath3.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath3.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath3.java
new file mode 100644
index 0000000..91185ce
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath3.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 3 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath3<T1, T2, T3>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3> TypedZPath3<T1, T2, T3> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3> TypedZPath3<T1, T2, T3> from(ZPath path)
+    {
+        return (p1, p2, p3) -> path.resolved(p1, p2, p3);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath4.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath4.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath4.java
new file mode 100644
index 0000000..be4905b
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath4.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 4 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath4<T1, T2, T3, T4>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3, T4 p4);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4> TypedZPath4<T1, T2, T3, T4> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4> TypedZPath4<T1, T2, T3, T4> from(ZPath path)
+    {
+        return (p1, p2, p3, p4) -> path.resolved(p1, p2, p3, p4);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath5.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath5.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath5.java
new file mode 100644
index 0000000..8c7311d
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath5.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 5 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath5<T1, T2, T3, T4, T5>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5> TypedZPath5<T1, T2, T3, T4, T5> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5> TypedZPath5<T1, T2, T3, T4, T5> from(ZPath path)
+    {
+        return (p1, p2, p3, p4, p5) -> path.resolved(p1, p2, p3, p4, p5);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath6.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath6.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath6.java
new file mode 100644
index 0000000..6e05be9
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath6.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 6 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath6<T1, T2, T3, T4, T5, T6>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6> TypedZPath6<T1, T2, T3, T4, T5, T6> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6> TypedZPath6<T1, T2, T3, T4, T5, T6> from(ZPath path)
+    {
+        return (p1, p2, p3, p4, p5, p6) -> path.resolved(p1, p2, p3, p4, p5, p6);
+    }
+}


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

Posted by ra...@apache.org.
Squashed commit of the following:

commit 588dca66e6b60161bd66488edd2df211a8e2c3a6
Merge: 7e6551f3 8b28b120
Author: randgalt <ra...@apache.org>
Date:   Fri Jul 7 22:28:15 2017 -0500

    Merge branch 'master' into CURATOR-397

commit 7e6551f3fe3f1abbcf797739df20acc5a4e5be16
Author: randgalt <ra...@apache.org>
Date:   Thu Jun 29 23:21:02 2017 -0500

    completeChildrenAsZNodes() wasn't handling empty children listsw

commit 738c3617ee4887fefcf8dd5082b9b71c9f7021db
Author: randgalt <ra...@apache.org>
Date:   Thu Jun 29 23:20:45 2017 -0500

    no need to use EnsureContainers object

commit 09f9bc06accf7c42d3a5dcf3d92c20d706e56ea6
Author: randgalt <ra...@apache.org>
Date:   Wed Jun 28 09:46:32 2017 -0500

    Added asyncEnsureContainers

commit f7d410f8e11a840a36c7bb97313ed3fb7edf73ca
Author: randgalt <ra...@apache.org>
Date:   Wed Jun 28 09:19:03 2017 -0500

    It doesn't make sense for cached() to cache the root node. So, don't

commit b83bef9de0c980dfe21c85d0c08abb1bd75a06f5
Author: randgalt <ra...@apache.org>
Date:   Wed Jun 28 00:07:29 2017 -0500

    oops - orSetData() was returning null

commit 318bed14f050dd579f76a768bcc7c3125d832049
Author: randgalt <ra...@apache.org>
Date:   Tue Jun 27 00:10:24 2017 -0500

    version of AsyncLocker.release() that ignores unheld locks

commit eeabe8b5136e8a46569ede9ea193f6e2f10153b1
Author: randgalt <ra...@apache.org>
Date:   Wed Jun 14 16:10:35 2017 -0500

    completeChildrenAsZNodes was treating zPaths as node names

commit f0bcd0476e30a876f7995d191d8811fadb1a201e
Author: randgalt <ra...@apache.org>
Date:   Tue Jun 13 07:34:25 2017 -0500

    Added lockAsyncIf to AsyncLock

commit fbf25adcf88b4198fdecd18a494b615c70bf763c
Author: randgalt <ra...@apache.org>
Date:   Sat Jun 10 22:22:08 2017 -0500

    Made the API of AsyncLocker cleaner

commit e943763f0d299b5e69e6a7f4e871024b7aa95503
Author: randgalt <ra...@apache.org>
Date:   Sat Jun 10 20:46:19 2017 -0500

    Added AsyncLocker

commit 93f11ed09e0cc6e9c6b80b1a88796d027f4f3d7a
Author: randgalt <ra...@apache.org>
Date:   Sat Jun 10 19:52:15 2017 -0500

    1. remove siblings added childrenAsZNodes
    2. Add util to unwrap stages of ZNodes

commit 55df07e6ab5417943d485e88f7efa89df0b6e52d
Author: randgalt <ra...@apache.org>
Date:   Fri Jun 9 23:55:20 2017 -0500

    Added list()

commit f32a5fb4eeb63251ad769f54f7fee255c2bee61e
Author: randgalt <ra...@apache.org>
Date:   Fri Jun 9 23:54:51 2017 -0500

    Added '0' versions of Typed classes as a convenience so that the same idiom can be used even if there are no real type arguments

commit 523f0c8095fcde7033b9caa4c5423301085b0e16
Author: randgalt <ra...@apache.org>
Date:   Fri Jun 9 18:01:58 2017 -0500

    Added readThrough methods

commit 6428a9238892eba6e9f89551ed4602d2a7c5a04f
Author: randgalt <ra...@apache.org>
Date:   Thu May 18 15:32:38 2017 +0200

    Added a test for ACLs

commit 7a15af6ac5a58f1aba5dd6569d11b3a46fc45f9d
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 17:10:43 2017 +0200

    CachedModeledFramework now handles unresolved paths where the final node is a parameter.

commit bb36c48ef420d48e345368d03e4696f22d394126
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 16:53:30 2017 +0200

    updated doc for new auto-resolve behavior

commit 3338c05c997068c93fe4f889f4d0c1ee3fc3a776
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 16:03:53 2017 +0200

    renamed at() to child()

commit 05cab2ec57208d3aa87202ff2d4f44146d5ba6b5
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 16:01:59 2017 +0200

    Updated sub/pub test to use new resolving features so that at() isn't necessary

commit 6ea7221ab48dd0597e3b0f2ba4cdc33147ff7d4d
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 15:55:31 2017 +0200

    1. Support partially resolved paths
    2. Added method to get siblings
    3. Auto resolve unresolved paths on set/update using the model being set/updated

commit 7ed263cc37d1c6eacfe676f7c78096d7086b9c59
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 14:04:57 2017 +0200

    doc fixes

commit ed00d50f81cb224f9e2690aaa425adae6b0da0bd
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 13:52:51 2017 +0200

    doc fixes

commit 6037c7b270b12dce273aa67939a78a4c00b5155f
Author: randgalt <ra...@apache.org>
Date:   Thu May 11 12:53:49 2017 +0200

    further refinement of previous change. Not all completion handlers  should be in the cache's thread. But, allow for the default async to use it

commit 396d98a51495ec1c60156dcd0d6d553644e43689
Author: randgalt <ra...@apache.org>
Date:   Wed May 10 23:38:07 2017 +0200

    More tests, refactoring

commit 6485f1650e37e392e6122f8b13ff432ee0321666
Author: randgalt <ra...@apache.org>
Date:   Wed May 10 19:23:00 2017 +0200

    CachedModeledFrameworkImpl wasn't using executor in right way. Completions need to happen async not setting of the value. This required creating CachedStage which proxies all non-async methods to their async counterparts using the CachedModeledFrameworkImpl's executor

commit 80ca587e5e543568f06780ad8a8f874b94333218
Author: randgalt <ra...@apache.org>
Date:   Wed May 10 16:09:55 2017 +0200

    more tests

commit 49743a487e3c159029906bb334989e6f1c82f6a8
Author: randgalt <ra...@apache.org>
Date:   Wed May 10 15:00:05 2017 +0200

    Allow the {id} in ZPaths to use any value. This is very useful for making paths easier to read and debug. So, you can now do: '/root/{org}/employee/{emp id}'

commit cba43b342400461e0d4f0dfd894b69a8b1891438
Author: randgalt <ra...@apache.org>
Date:   Wed May 10 14:09:12 2017 +0200

    doc updates

commit 13e17cbbfe089554f47d64449eea1302b9b6bd19
Author: randgalt <ra...@apache.org>
Date:   Wed May 10 12:43:59 2017 +0200

    minor update

commit a7d2e058e4fedb00c408694f185b54ec4259bf94
Author: randgalt <ra...@apache.org>
Date:   Wed May 10 11:58:04 2017 +0200

    final versioned APIs and doc

commit beac06f136f26dd3dabb754dcb999876d614244b
Author: randgalt <ra...@apache.org>
Date:   Tue May 9 23:16:54 2017 +0200

    Added Versioned facade for easy management of models with versions

commit 23a1487984b576b878e3a88287f74d9381e5dec7
Author: randgalt <ra...@apache.org>
Date:   Mon May 8 19:11:22 2017 +0200

    Work on testing modeled schema

commit 555e1d4684e233e1caa910be67a4ff892253d9c7
Merge: e4a7e091 32a7755b
Author: randgalt <ra...@apache.org>
Date:   Mon May 8 18:36:48 2017 +0200

    Merge branch 'master' into CURATOR-397

commit e4a7e09172c7d9d14081233dec88690ecba6df9e
Author: randgalt <ra...@apache.org>
Date:   Mon May 8 05:42:17 2017 +0200

    some refactoring, refinement

commit c002e22e5a613cd8426c2e8b407ab06957908526
Author: randgalt <ra...@apache.org>
Date:   Sun May 7 10:26:48 2017 +0200

    removed ZPath 'resolving'. It doesn't add much value and muddies up the code

commit e95b885ebf0240e61d8de1c2644f8fb11cc2eca5
Author: randgalt <ra...@apache.org>
Date:   Sun May 7 09:58:07 2017 +0200

    Allow setting a version for orSetData()

commit 1110ab3bbc55748c053adcd909cc0d82be84309d
Author: randgalt <ra...@apache.org>
Date:   Sun May 7 09:55:28 2017 +0200

    Support setting the version when doing a create or set operation

commit c18783924af4b4c3f11d098165c85bf313f454c2
Author: randgalt <ra...@apache.org>
Date:   Sun May 7 09:41:55 2017 +0200

    Added variant to readAsZNode

commit fe0a88b3b7b9fccf9da022bc0a13e440b1f8435c
Author: randgalt <ra...@apache.org>
Date:   Fri May 5 18:38:46 2017 -0400

    better exception handling for serialization issues

commit f2370b7710b85850936f48404c6959d45ed03626
Author: randgalt <ra...@apache.org>
Date:   Thu May 4 23:15:14 2017 -0500

    1. Allow for an Executor service to be passed to the cache
    2. CachedModeledFramework must complete the stages via a thread as the caller is expected async processing

commit 29b09ceb5d305419428760e07d572dd4b920cb74
Author: randgalt <ra...@apache.org>
Date:   Thu May 4 15:35:05 2017 -0500

    updated doc

commit 0bc3a9bd2e63bee511b0d78f584f862e875c8365
Author: randgalt <ra...@apache.org>
Date:   Thu May 4 10:30:13 2017 -0500

    some refactoring and doc

commit 1793675c136bf0ba89a33f9c5490e9ab5c7f0000
Author: randgalt <ra...@apache.org>
Date:   Thu May 4 10:16:55 2017 -0500

    Allow type model specs and typed clients to be created in "one shot". This made writing the sub-pub example simpler and easier to understand.

commit 5ac1a3314e372d8bd22ef7bba704b5621a1638b8
Author: randgalt <ra...@apache.org>
Date:   Wed May 3 22:59:47 2017 -0500

    finished docs for the pub-sub example

commit c97fae4ef6a06301f2a794df6e23411b303f8cf1
Merge: 71e28e1a f898959e
Author: randgalt <ra...@apache.org>
Date:   Wed May 3 22:18:38 2017 -0500

    Merge branch 'master' into CURATOR-397

commit 71e28e1a6b97171b3c54da0a4dc82564247e7f24
Author: randgalt <ra...@apache.org>
Date:   Wed May 3 21:50:59 2017 -0500

    starting read me for the sub-pub example

commit aa86931b9392b0d6f0cf6ff161afd82e9e3749f1
Author: randgalt <ra...@apache.org>
Date:   Wed May 3 17:02:31 2017 -0500

    added lots of doc

commit ea47c6c990c9dab9f91fa76c074d1a4251df2f9e
Author: randgalt <ra...@apache.org>
Date:   Wed May 3 14:20:43 2017 -0500

    Another big refactoring. NodeName only needs to be used internally by ZPath as all other resolve methods forward to it. This is cleaner.

commit c8a57d5387d9d34287b9e47808f47832e7758b3e
Merge: 37927efa d7d84c55
Author: randgalt <ra...@apache.org>
Date:   Wed May 3 08:17:36 2017 -0500

    Merge branch 'CURATOR-407' into CURATOR-397

commit 37927efa65ca755407f1032ef45cb46a11372184
Author: randgalt <ra...@apache.org>
Date:   Wed May 3 07:42:13 2017 -0500

    refactoring

commit 69f1829d0f63badb0e16ff0bd4d53f3c4a8df541
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 22:01:29 2017 -0500

    added some doc

commit 0917e314b4cbb75e24e86c942b9d6501f84e8572
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 21:37:38 2017 -0500

    pub-sub example is now working - needs copious docs

commit 713bf4670e494a74b083431929b86dbfe6bd7f75
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 20:45:23 2017 -0500

    Work-in-progress. Using example sub/pub to flesh out issues and as an integration test. Working on bugs found

commit 1fcb63a5c7f29ddbfaedfdd18273ccabff21990e
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 17:32:38 2017 -0500

    Working on strongly typed parameters plus an example that uses it

commit 26c7adbb04a8c20a7d04111253c82ddef28e4f26
Merge: ef9df2b7 ed3082ec
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 15:27:27 2017 -0500

    Merge branch 'master' into CURATOR-397

commit ef9df2b7915ced99e227e2c2cd6f9ec12c7d7309
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 12:58:50 2017 -0500

    wip

commit 56c6c85a2f26ef914574139e4c1c657b5a7573a5
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 01:11:13 2017 -0500

    fixed startsWith

commit c3db1810a10e442626ee0c2f759a3f61da19375a
Author: randgalt <ra...@apache.org>
Date:   Tue May 2 00:12:02 2017 -0500

    Abstraction for creating ZPaths with strongly typed parameters

commit 6e21af2c565d95b7c281dd5211487a0682ab1a1a
Author: randgalt <ra...@apache.org>
Date:   Mon May 1 23:07:58 2017 -0500

    refactoring

commit 2cbbf999294181dbefeccebabd9c6de867142e2c
Author: randgalt <ra...@apache.org>
Date:   Mon May 1 22:56:36 2017 -0500

    Major rework of caching. Having the wrapped caches adds little value. Focus on the integrated caching in the modeled client instance

commit e40ed181655a83e705e6d74704d2fa2d1f93a7bc
Merge: 3d593105 35f5d274
Author: randgalt <ra...@apache.org>
Date:   Mon May 1 14:55:59 2017 -0500

    Merge branch 'CURATOR-3.0' into CURATOR-397

commit 3d593105d852ece596f7a55f312528ed23ccb69f
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 30 19:09:28 2017 -0500

    more work on new caching apis

commit b58d1ccba2878c0b2f12928b4e957f536c24fed8
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 30 15:43:03 2017 -0500

    resurrected TestCachedModeledCuratorFramework

commit 8418c5604cfeb186a3bd94e310a8001ab2bd5ee6
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 30 15:36:56 2017 -0500

    licenses

commit 16fb7b18b77153578dd489d0e1509a11eff0c6f5
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 30 15:36:18 2017 -0500

    Added new method of integrated caching. Needs testing, etc.

commit a4636098f6a6a679c06f337f981fd17d6b383218
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 30 14:12:18 2017 -0500

    make parameter the empty string

commit 70936c8c479558f737d3e86914af58d3855aa964
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 30 14:08:28 2017 -0500

    Added readChildren()

commit 50b054cc6af009c721e859398efcef5023f3cd3b
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 30 14:02:07 2017 -0500

    removed integrated caching for now - it was getting too cumbersome. I have some other ideas

commit 11cb97036ebc8639bd22799d99569511deac098b
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 29 13:52:36 2017 -0500

    more examples, added transactions

commit fa6bff6e5b34960fb8df37097561d4b02d47be24
Author: randgalt <ra...@apache.org>
Date:   Fri Apr 28 12:11:08 2017 -0500

    updated example

commit 38f6306ba109a61709e88b5acad233d0ba8dff26
Author: randgalt <ra...@apache.org>
Date:   Wed Apr 26 17:05:39 2017 -0500

    Added resolving methods to the model spec too

commit 52b334c30d82227ad7da3559976fa932fbbc5763
Author: randgalt <ra...@apache.org>
Date:   Wed Apr 26 16:53:17 2017 -0500

    added a resolving version that takes suppliers for parameters

commit 22fdb298db42a10ab74902b623e1696d22c2021f
Author: randgalt <ra...@apache.org>
Date:   Wed Apr 26 16:10:59 2017 -0500

    Added some tests/fixes

commit 5ebcfa32d7b00d5c2fc2eb4268a46fea2f98083d
Author: randgalt <ra...@apache.org>
Date:   Wed Apr 26 15:45:10 2017 -0500

    start of a mechanism to have variable/parameterized paths

commit 0cafc9199f3136f4f22703793bd56c73319d3161
Author: randgalt <ra...@apache.org>
Date:   Tue Apr 25 14:29:35 2017 -0500

    fixed typo bug with the cached values in ZPath - also made them more efficient

commit 3a3e6dd2d90fd2cf70507f247d1dcc1e59204721
Author: randgalt <ra...@apache.org>
Date:   Tue Apr 25 13:30:11 2017 -0500

    Created/abstracted CuratorModelSpec so that models can be specified independently of a CuratorFramework instance, etc. i.e. they can all be created at startup. The CuratorModelSpec also can generate a Schema object

commit 63225ba7562c48d9ac53bfaa50aa8ff0a790eb9c
Author: randgalt <ra...@apache.org>
Date:   Tue Apr 25 12:33:30 2017 -0500

    basics of write-through caching added. needs more work and testing

commit 6188fe6cee6b2a433f1c686adc660216a0aa1648
Author: randgalt <ra...@apache.org>
Date:   Tue Apr 18 09:43:48 2017 -0500

    Added getChildren

commit e512b5ee52bb9515493d0c077728acd6359caabd
Merge: 691f17d0 00ffe779
Author: randgalt <ra...@apache.org>
Date:   Mon Apr 17 19:43:32 2017 -0500

    Merge branch 'CURATOR-3.0' into CURATOR-397

commit 691f17d034647359a0d4e29f04f674cf589fb00b
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 17:01:03 2017 -0500

    update example index

commit 7c8f3fb62f6c9c7042cf07e78df7c00427392278
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 16:58:58 2017 -0500

    added more examples

commit 792aef2094e215edf77bf836b0f4dc43bb2cb232
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 14:27:15 2017 -0500

    renamed ModeledCuratorFramework so it isn't so long

commit 34c594a8e6c3730f06bb159709265e0d50b389db
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 13:21:49 2017 -0500

    fixed some typos and a bad link in the examples help

commit aadb72b62d6c781704f4ac977717c07a86a2694d
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 08:53:05 2017 -0500

    cleaned up ModeledCacheEvent definition

commit bf43232bd9f30efa0482203f895e4adb207ab247
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 08:47:02 2017 -0500

    doc

commit e0a27daef0b4dd363171b9ffde6eb4230cd8efd6
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 07:03:50 2017 -0500

    Make ModeledNodeCache use the same listener as the others

commit 301e989268a81537645bbdc3c71af4166550abd1
Author: randgalt <ra...@apache.org>
Date:   Sun Apr 9 06:40:34 2017 -0500

    finished initial tests

commit d518417e5c1bd2d5e4cd1a31833a28eb9bc9bc0a
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 23:29:39 2017 -0500

    tests

commit 45daa69bbf2a9ca0cfaa4c1ac20d53ce0314feb9
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 23:00:16 2017 -0500

    ModeledDetails isn't needed

commit dd390b6e1b100add85dd9d340dfbae9873b84bd0
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 22:35:51 2017 -0500

    doc

commit e8c68188d04df30eb91751f09360bb267fbe1c25
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 22:35:33 2017 -0500

    doc

commit 019caeea6aba36e77c64073003e66d62aa60b761
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 22:14:06 2017 -0500

    added tree cache wrapper

commit 4efc38f3d16d111c3a96c4eb28212b2cc8a08188
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 21:36:07 2017 -0500

    wip on wrapping caches

commit 0f0db1c386471dd4babdf3f26b37b1d9056b3f7d
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 15:15:12 2017 -0500

    more doc

commit 1dab81b5afedc4a0669312b386936baf7715c080
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 15:03:49 2017 -0500

    adding docs

commit 8237fde0aabf7088b6ddcaf56a7fba1f8b1d3441
Author: randgalt <ra...@apache.org>
Date:   Sat Apr 8 13:52:06 2017 -0500

    WIP - strongly type DSL for Curator


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

Branch: refs/heads/master
Commit: 0f5d10da38626494ae4d0e3339a2a35f270b0edd
Parents: 8b28b12
Author: randgalt <ra...@apache.org>
Authored: Fri Jul 7 22:31:42 2017 -0500
Committer: randgalt <ra...@apache.org>
Committed: Fri Jul 7 22:31:42 2017 -0500

----------------------------------------------------------------------
 curator-examples/pom.xml                        |  23 +-
 .../src/main/java/async/AsyncExamples.java      | 127 ++++++
 .../src/main/java/modeled/ContainerType.java    |  68 +++
 .../java/modeled/ModeledCuratorExamples.java    |  67 +++
 .../java/modeled/ModeledCuratorExamplesAlt.java |  52 +++
 .../src/main/java/modeled/PersonId.java         |  70 +++
 .../src/main/java/modeled/PersonModel.java      | 120 +++++
 .../src/main/java/modeled/PersonModelSpec.java  |  46 ++
 .../src/main/java/pubsub/Clients.java           |  76 ++++
 .../src/main/java/pubsub/Publisher.java         | 146 ++++++
 curator-examples/src/main/java/pubsub/README.md |  93 ++++
 .../src/main/java/pubsub/SubPubTest.java        | 220 +++++++++
 .../src/main/java/pubsub/Subscriber.java        |  84 ++++
 .../java/pubsub/messages/LocationAvailable.java |  55 +++
 .../main/java/pubsub/messages/UserCreated.java  |  64 +++
 .../src/main/java/pubsub/models/Group.java      |  47 ++
 .../src/main/java/pubsub/models/Instance.java   |  76 ++++
 .../main/java/pubsub/models/InstanceType.java   |  27 ++
 .../src/main/java/pubsub/models/Message.java    |  67 +++
 .../src/main/java/pubsub/models/Priority.java   |  26 ++
 .../src/main/resources/log4j.properties         |  24 +
 .../src/site/confluence/index.confluence        |   5 +-
 .../curator/framework/api/CreateBuilder.java    |  10 +
 .../framework/imps/CreateBuilderImpl.java       |  17 +-
 .../apache/curator/framework/schema/Schema.java |   2 +-
 .../framework/recipes/cache/NodeCache.java      |  15 +
 .../recipes/cache/PathChildrenCache.java        |   2 +-
 .../apache/curator/test/BaseClassForTests.java  |  10 +-
 curator-x-async/pom.xml                         |  18 +
 .../org/apache/curator/x/async/AsyncStage.java  |  15 +-
 .../apache/curator/x/async/AsyncWrappers.java   | 297 ++++++++++++
 .../curator/x/async/api/AsyncCreateBuilder.java |  28 ++
 .../x/async/api/AsyncCuratorFrameworkDsl.java   |   1 -
 .../x/async/details/AsyncCreateBuilderImpl.java |  21 +
 .../x/async/modeled/JacksonModelSerializer.java | 124 +++++
 .../x/async/modeled/ModelSerializer.java        |  43 ++
 .../curator/x/async/modeled/ModelSpec.java      | 217 +++++++++
 .../x/async/modeled/ModelSpecBuilder.java       | 138 ++++++
 .../x/async/modeled/ModeledFramework.java       | 371 +++++++++++++++
 .../async/modeled/ModeledFrameworkBuilder.java  | 154 +++++++
 .../curator/x/async/modeled/NodeName.java       |  39 ++
 .../curator/x/async/modeled/Resolvable.java     |  48 ++
 .../apache/curator/x/async/modeled/ZNode.java   |  74 +++
 .../apache/curator/x/async/modeled/ZPath.java   | 279 ++++++++++++
 .../modeled/cached/CachedModeledFramework.java  | 123 +++++
 .../x/async/modeled/cached/ModeledCache.java    |  46 ++
 .../modeled/cached/ModeledCacheListener.java    | 106 +++++
 .../details/CachedModeledFrameworkImpl.java     | 342 ++++++++++++++
 .../x/async/modeled/details/ModelSpecImpl.java  | 239 ++++++++++
 .../x/async/modeled/details/ModelStage.java     | 171 +++++++
 .../async/modeled/details/ModeledCacheImpl.java | 211 +++++++++
 .../modeled/details/ModeledFrameworkImpl.java   | 450 +++++++++++++++++++
 .../details/VersionedModeledFrameworkImpl.java  |  85 ++++
 .../x/async/modeled/details/ZNodeImpl.java      |  56 +++
 .../x/async/modeled/details/ZPathImpl.java      | 289 ++++++++++++
 .../x/async/modeled/typed/TypedModelSpec.java   |  87 ++++
 .../x/async/modeled/typed/TypedModelSpec0.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec10.java |  61 +++
 .../x/async/modeled/typed/TypedModelSpec2.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec3.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec4.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec5.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec6.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec7.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec8.java  |  61 +++
 .../x/async/modeled/typed/TypedModelSpec9.java  |  61 +++
 .../modeled/typed/TypedModeledFramework.java    |  93 ++++
 .../modeled/typed/TypedModeledFramework0.java   |  63 +++
 .../modeled/typed/TypedModeledFramework10.java  |  63 +++
 .../modeled/typed/TypedModeledFramework2.java   |  63 +++
 .../modeled/typed/TypedModeledFramework3.java   |  63 +++
 .../modeled/typed/TypedModeledFramework4.java   |  63 +++
 .../modeled/typed/TypedModeledFramework5.java   |  63 +++
 .../modeled/typed/TypedModeledFramework6.java   |  63 +++
 .../modeled/typed/TypedModeledFramework7.java   |  63 +++
 .../modeled/typed/TypedModeledFramework8.java   |  63 +++
 .../modeled/typed/TypedModeledFramework9.java   |  63 +++
 .../x/async/modeled/typed/TypedZPath.java       |  92 ++++
 .../x/async/modeled/typed/TypedZPath0.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath10.java     |  52 +++
 .../x/async/modeled/typed/TypedZPath2.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath3.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath4.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath5.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath6.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath7.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath8.java      |  52 +++
 .../x/async/modeled/typed/TypedZPath9.java      |  52 +++
 .../x/async/modeled/versioned/Versioned.java    |  69 +++
 .../versioned/VersionedModeledFramework.java    |  56 +++
 .../src/site/confluence/async.confluence        | 212 +++++++++
 .../src/site/confluence/index.confluence        | 213 +--------
 .../confluence/modeled-components.confluence    | 186 ++++++++
 .../site/confluence/modeled-typed.confluence    |  89 ++++
 .../src/site/confluence/modeled.confluence      |  48 ++
 curator-x-async/src/site/site.xml               |   6 +-
 .../x/async/CompletableBaseClassForTests.java   |  65 +++
 .../curator/x/async/TestAsyncWrappers.java      |  73 +++
 .../curator/x/async/TestBasicOperations.java    |  35 +-
 .../modeled/TestCachedModeledFramework.java     | 167 +++++++
 .../x/async/modeled/TestModeledFramework.java   | 178 ++++++++
 .../async/modeled/TestModeledFrameworkBase.java |  64 +++
 .../curator/x/async/modeled/TestZPath.java      | 126 ++++++
 .../x/async/modeled/models/TestModel.java       | 115 +++++
 .../x/async/modeled/models/TestNewerModel.java  | 137 ++++++
 .../x/async/modeled/models/TestSimpleModel.java |  84 ++++
 .../src/test/resources/log4j.properties         |  27 ++
 curator-x-rpc/src/site/site.xml                 |   2 +-
 pom.xml                                         |  12 +
 src/site/site.xml                               |   4 +-
 110 files changed, 9380 insertions(+), 244 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/pom.xml
----------------------------------------------------------------------
diff --git a/curator-examples/pom.xml b/curator-examples/pom.xml
index f612cc2..cc65570 100644
--- a/curator-examples/pom.xml
+++ b/curator-examples/pom.xml
@@ -50,9 +50,30 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.curator</groupId>
+            <artifactId>curator-x-async</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+
+        <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-log4j12</artifactId>
-            <scope>test</scope>
         </dependency>
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/async/AsyncExamples.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/async/AsyncExamples.java b/curator-examples/src/main/java/async/AsyncExamples.java
new file mode 100644
index 0000000..43db219
--- /dev/null
+++ b/curator-examples/src/main/java/async/AsyncExamples.java
@@ -0,0 +1,127 @@
+/**
+ * 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 async;
+
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.AsyncEventException;
+import org.apache.curator.x.async.WatchMode;
+import org.apache.zookeeper.WatchedEvent;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * Examples using the asynchronous DSL
+ */
+public class AsyncExamples
+{
+    public static AsyncCuratorFramework wrap(CuratorFramework client)
+    {
+        // wrap a CuratorFramework instance so that it can be used async.
+        // do this once and re-use the returned AsyncCuratorFramework instance
+        return AsyncCuratorFramework.wrap(client);
+    }
+
+    public static void create(CuratorFramework client, String path, byte[] payload)
+    {
+        AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);   // normally you'd wrap early in your app and reuse the instance
+
+        // create a node at the given path with the given payload asynchronously
+        async.create().forPath(path, payload).whenComplete((name, exception) -> {
+            if ( exception != null )
+            {
+                // there was a problem
+                exception.printStackTrace();
+            }
+            else
+            {
+                System.out.println("Created node name is: " + name);
+            }
+        });
+    }
+
+    public static void createThenWatch(CuratorFramework client, String path)
+    {
+        AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);   // normally you'd wrap early in your app and reuse the instance
+
+        // this example shows to asynchronously use watchers for both event
+        // triggering and connection problems. If you don't need to be notified
+        // of connection problems, use the simpler approach shown in createThenWatchSimple()
+
+        // create a node at the given path with the given payload asynchronously
+        // then watch the created node
+        async.create().forPath(path).whenComplete((name, exception) -> {
+            if ( exception != null )
+            {
+                // there was a problem creating the node
+                exception.printStackTrace();
+            }
+            else
+            {
+                handleWatchedStage(async.watched().checkExists().forPath(path).event());
+            }
+        });
+    }
+
+    public static void createThenWatchSimple(CuratorFramework client, String path)
+    {
+        AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);   // normally you'd wrap early in your app and reuse the instance
+
+        // create a node at the given path with the given payload asynchronously
+        // then watch the created node
+        async.create().forPath(path).whenComplete((name, exception) -> {
+            if ( exception != null )
+            {
+                // there was a problem creating the node
+                exception.printStackTrace();
+            }
+            else
+            {
+                // because "WatchMode.successOnly" is used the watch stage is only triggered when
+                // the EventType is a node event
+                async.with(WatchMode.successOnly).watched().checkExists().forPath(path).event().thenAccept(event -> {
+                    System.out.println(event.getType());
+                    System.out.println(event);
+                });
+            }
+        });
+    }
+
+    private static void handleWatchedStage(CompletionStage<WatchedEvent> watchedStage)
+    {
+        // async handling of Watchers is complicated because watchers can trigger multiple times
+        // and CompletionStage don't support this behavior
+
+        // thenAccept() handles normal watcher triggering.
+        watchedStage.thenAccept(event -> {
+            System.out.println(event.getType());
+            System.out.println(event);
+            // etc.
+        });
+
+        // exceptionally is called if there is a connection problem in which case
+        // watchers trigger to signal the connection problem. "reset()" must be called
+        // to reset the watched stage
+        watchedStage.exceptionally(exception -> {
+            AsyncEventException asyncEx = (AsyncEventException)exception;
+            asyncEx.printStackTrace();    // handle the error as needed
+            handleWatchedStage(asyncEx.reset());
+            return null;
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/modeled/ContainerType.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/modeled/ContainerType.java b/curator-examples/src/main/java/modeled/ContainerType.java
new file mode 100644
index 0000000..a36cfaa
--- /dev/null
+++ b/curator-examples/src/main/java/modeled/ContainerType.java
@@ -0,0 +1,68 @@
+/**
+ * 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 modeled;
+
+public class ContainerType
+{
+    private final int typeId;
+
+    public ContainerType()
+    {
+        this(0);
+    }
+
+    public ContainerType(int typeId)
+    {
+        this.typeId = typeId;
+    }
+
+    public int getTypeId()
+    {
+        return typeId;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        ContainerType that = (ContainerType)o;
+
+        return typeId == that.typeId;
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return typeId;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "ContainerType{" + "typeId=" + typeId + '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/modeled/ModeledCuratorExamples.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/modeled/ModeledCuratorExamples.java b/curator-examples/src/main/java/modeled/ModeledCuratorExamples.java
new file mode 100644
index 0000000..e7e363c
--- /dev/null
+++ b/curator-examples/src/main/java/modeled/ModeledCuratorExamples.java
@@ -0,0 +1,67 @@
+/**
+ * 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 modeled;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.JacksonModelSerializer;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ZPath;
+import java.util.function.Consumer;
+
+public class ModeledCuratorExamples
+{
+    public static ModeledFramework<PersonModel> wrap(AsyncCuratorFramework client)
+    {
+        JacksonModelSerializer<PersonModel> serializer = JacksonModelSerializer.build(PersonModel.class);
+
+        // build a model specification - you can pre-build all the model specifications for your app at startup
+        ModelSpec<PersonModel> modelSpec = ModelSpec.builder(ZPath.parse("/example/path"), serializer).build();
+
+        // wrap a CuratorFramework instance so that it can be used "modeled".
+        // do this once and re-use the returned ModeledFramework instance.
+        // ModeledFramework instances are tied to a given path
+        return ModeledFramework.wrap(client, modelSpec);
+    }
+
+    public static void createOrUpdate(ModeledFramework<PersonModel> modeled, PersonModel model)
+    {
+        // change the affected path to be modeled's base path plus id: i.e. "/example/path/{id}"
+        ModeledFramework<PersonModel> atId = modeled.child(model.getId().getId());
+
+        // by default ModeledFramework instances update the node if it already exists
+        // so this will either create or update the node
+        atId.set(model); // note - this is async
+    }
+
+    public static void readPerson(ModeledFramework<PersonModel> modeled, String id, Consumer<PersonModel> receiver)
+    {
+        // read the person with the given ID and asynchronously call the receiver after it is read
+        modeled.child(id).read().whenComplete((person, exception) -> {
+            if ( exception != null )
+            {
+                exception.printStackTrace();    // handle the error
+            }
+            else
+            {
+                receiver.accept(person);
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/modeled/ModeledCuratorExamplesAlt.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/modeled/ModeledCuratorExamplesAlt.java b/curator-examples/src/main/java/modeled/ModeledCuratorExamplesAlt.java
new file mode 100644
index 0000000..859a1f1
--- /dev/null
+++ b/curator-examples/src/main/java/modeled/ModeledCuratorExamplesAlt.java
@@ -0,0 +1,52 @@
+/**
+ * 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 modeled;
+
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import java.util.function.Consumer;
+
+public class ModeledCuratorExamplesAlt
+{
+    public static void createOrUpdate(PersonModelSpec modelSpec, PersonModel model)
+    {
+        // change the affected path to be modeled's base path plus id: i.e. "/example/path/{id}"
+        ModeledFramework<PersonModel> resolved = modelSpec.resolved(model.getContainerType(), model.getId());
+
+        // by default ModeledFramework instances update the node if it already exists
+        // so this will either create or update the node
+        resolved.set(model); // note - this is async
+    }
+
+    public static void readPerson(PersonModelSpec modelSpec, ContainerType containerType, PersonId id, Consumer<PersonModel> receiver)
+    {
+        ModeledFramework<PersonModel> resolved = modelSpec.resolved(containerType, id);
+
+        // read the person with the given ID and asynchronously call the receiver after it is read
+        resolved.read().whenComplete((person, exception) -> {
+            if ( exception != null )
+            {
+                exception.printStackTrace();    // handle the error
+            }
+            else
+            {
+                receiver.accept(person);
+            }
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/modeled/PersonId.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/modeled/PersonId.java b/curator-examples/src/main/java/modeled/PersonId.java
new file mode 100644
index 0000000..eabc286
--- /dev/null
+++ b/curator-examples/src/main/java/modeled/PersonId.java
@@ -0,0 +1,70 @@
+/**
+ * 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 modeled;
+
+import java.util.Objects;
+
+public class PersonId
+{
+    private final String id;
+
+    public PersonId()
+    {
+        this("");
+    }
+
+    public PersonId(String id)
+    {
+        this.id = Objects.requireNonNull(id, "id cannot be null");
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        PersonId personId = (PersonId)o;
+
+        return id.equals(personId.id);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        return id.hashCode();
+    }
+
+    @Override
+    public String toString()
+    {
+        return "PersonId{" + "id='" + id + '\'' + '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/modeled/PersonModel.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/modeled/PersonModel.java b/curator-examples/src/main/java/modeled/PersonModel.java
new file mode 100644
index 0000000..f9b9102
--- /dev/null
+++ b/curator-examples/src/main/java/modeled/PersonModel.java
@@ -0,0 +1,120 @@
+/**
+ * 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 modeled;
+
+import java.util.Objects;
+
+public class PersonModel
+{
+    private final PersonId id;
+    private final ContainerType containerType;
+    private final String firstName;
+    private final String lastName;
+    private final int age;
+
+    public PersonModel()
+    {
+        this(new PersonId(), new ContainerType(), null, null, 0);
+    }
+
+    public PersonModel(PersonId id, ContainerType containerType, String firstName, String lastName, int age)
+    {
+        this.id = Objects.requireNonNull(id, "id cannot be null");
+        this.containerType = Objects.requireNonNull(containerType, "containerType cannot be null");
+        this.firstName = Objects.requireNonNull(firstName, "firstName cannot be null");
+        this.lastName = Objects.requireNonNull(lastName, "lastName cannot be null");
+        this.age = age;
+    }
+
+    public PersonId getId()
+    {
+        return id;
+    }
+
+    public ContainerType getContainerType()
+    {
+        return containerType;
+    }
+
+    public String getFirstName()
+    {
+        return firstName;
+    }
+
+    public String getLastName()
+    {
+        return lastName;
+    }
+
+    public int getAge()
+    {
+        return age;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        PersonModel that = (PersonModel)o;
+
+        if ( age != that.age )
+        {
+            return false;
+        }
+        if ( !id.equals(that.id) )
+        {
+            return false;
+        }
+        if ( !containerType.equals(that.containerType) )
+        {
+            return false;
+        }
+        //noinspection SimplifiableIfStatement
+        if ( !firstName.equals(that.firstName) )
+        {
+            return false;
+        }
+        return lastName.equals(that.lastName);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = id.hashCode();
+        result = 31 * result + containerType.hashCode();
+        result = 31 * result + firstName.hashCode();
+        result = 31 * result + lastName.hashCode();
+        result = 31 * result + age;
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "PersonModel{" + "id=" + id + ", containerType=" + containerType + ", firstName='" + firstName + '\'' + ", lastName='" + lastName + '\'' + ", age=" + age + '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/modeled/PersonModelSpec.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/modeled/PersonModelSpec.java b/curator-examples/src/main/java/modeled/PersonModelSpec.java
new file mode 100644
index 0000000..f90f616
--- /dev/null
+++ b/curator-examples/src/main/java/modeled/PersonModelSpec.java
@@ -0,0 +1,46 @@
+/**
+ * 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 modeled;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.JacksonModelSerializer;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.ZPath;
+
+public class PersonModelSpec
+{
+    private final AsyncCuratorFramework client;
+    private final ModelSpec<PersonModel> modelSpec;
+
+    public PersonModelSpec(AsyncCuratorFramework client)
+    {
+        this.client = client;
+
+        JacksonModelSerializer<PersonModel> serializer = JacksonModelSerializer.build(PersonModel.class);
+        ZPath path = ZPath.parseWithIds("/example/{id}/path/{id}");
+        modelSpec = ModelSpec.builder(path, serializer).build();
+    }
+
+    public ModeledFramework<PersonModel> resolved(ContainerType containerType, PersonId personId)
+    {
+        ModelSpec<PersonModel> resolved = modelSpec.resolved(containerType.getTypeId(), personId.getId());
+        return ModeledFramework.wrap(client, resolved);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/Clients.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/Clients.java b/curator-examples/src/main/java/pubsub/Clients.java
new file mode 100644
index 0000000..c0a8136
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/Clients.java
@@ -0,0 +1,76 @@
+/**
+ * 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;
+
+import org.apache.curator.x.async.modeled.JacksonModelSerializer;
+import org.apache.curator.x.async.modeled.ModelSpec;
+import org.apache.curator.x.async.modeled.ModelSpecBuilder;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.typed.TypedModeledFramework;
+import org.apache.curator.x.async.modeled.typed.TypedModeledFramework2;
+import org.apache.zookeeper.CreateMode;
+import pubsub.messages.LocationAvailable;
+import pubsub.messages.UserCreated;
+import pubsub.models.Group;
+import pubsub.models.Instance;
+import pubsub.models.InstanceType;
+import pubsub.models.Priority;
+import java.util.concurrent.TimeUnit;
+
+public class Clients
+{
+    /**
+     * A client template for LocationAvailable instances
+     */
+    public static final TypedModeledFramework2<LocationAvailable, Group, Priority> locationAvailableClient = TypedModeledFramework2.from(
+        ModeledFramework.builder(),
+        builder(LocationAvailable.class),
+        "/root/pubsub/messages/locations/{group}/{priority}/{id}"
+    );
+
+    /**
+     * A client template for UserCreated instances
+     */
+    public static final TypedModeledFramework2<UserCreated, Group, Priority> userCreatedClient = TypedModeledFramework2.from(
+        ModeledFramework.builder(),
+        builder(UserCreated.class),
+        "/root/pubsub/messages/users/{group}/{priority}/{id}"
+    );
+
+    /**
+     * A client template for Instance instances
+     */
+    public static final TypedModeledFramework<Instance, InstanceType> instanceClient = TypedModeledFramework.from(
+        ModeledFramework.builder(),
+        builder(Instance.class),
+        "/root/pubsub/instances/{instance-type}/{id}"
+    );
+
+    private static <T> ModelSpecBuilder<T> builder(Class<T> clazz)
+    {
+        return ModelSpec.builder(JacksonModelSerializer.build(clazz))
+            .withTtl(TimeUnit.MINUTES.toMillis(10)) // for our pub-sub example, messages are valid for 10 minutes
+            .withCreateMode(CreateMode.PERSISTENT_WITH_TTL)
+            ;
+    }
+
+    private Clients()
+    {
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/Publisher.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/Publisher.java b/curator-examples/src/main/java/pubsub/Publisher.java
new file mode 100644
index 0000000..5c8f7af
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/Publisher.java
@@ -0,0 +1,146 @@
+/**
+ * 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;
+
+import org.apache.curator.framework.api.transaction.CuratorOp;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.ModeledFramework;
+import org.apache.curator.x.async.modeled.typed.TypedModeledFramework2;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import pubsub.messages.LocationAvailable;
+import pubsub.messages.UserCreated;
+import pubsub.models.Group;
+import pubsub.models.Instance;
+import pubsub.models.Message;
+import pubsub.models.Priority;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class Publisher
+{
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private final AsyncCuratorFramework client;
+
+    public Publisher(AsyncCuratorFramework client)
+    {
+        this.client = Objects.requireNonNull(client, "client cannot be null");
+    }
+
+    /**
+     * Publish the given instance using the Instance client template
+     *
+     * @param instance instance to publish
+     */
+    public void publishInstance(Instance instance)
+    {
+        ModeledFramework<Instance> resolvedClient = Clients.instanceClient.resolved(client, instance.getType());
+        resolvedClient.set(instance).exceptionally(e -> {
+            log.error("Could not publish instance: " + instance, e);
+            return null;
+        });
+    }
+
+    /**
+     * Publish the given instances using the Instance client template in a transaction
+     *
+     * @param instances instances to publish
+     */
+    public void publishInstances(List<Instance> instances)
+    {
+        List<CuratorOp> operations = instances.stream()
+            .map(instance -> Clients.instanceClient
+                .resolved(client, instance.getType())
+                .createOp(instance)
+            )
+            .collect(Collectors.toList());
+        client.transaction().forOperations(operations).exceptionally(e -> {
+            log.error("Could not publish instances: " + instances, e);
+            return null;
+        });
+    }
+
+    /**
+     * Publish the given LocationAvailable using the LocationAvailable client template
+     *
+     * @param group group
+     * @param locationAvailable message to publish
+     */
+    public void publishLocationAvailable(Group group, LocationAvailable locationAvailable)
+    {
+        publishMessage(Clients.locationAvailableClient, group, locationAvailable);
+    }
+
+    /**
+     * Publish the given UserCreated using the UserCreated client template
+     *
+     * @param group group
+     * @param userCreated message to publish
+     */
+    public void publishUserCreated(Group group, UserCreated userCreated)
+    {
+        publishMessage(Clients.userCreatedClient, group, userCreated);
+    }
+
+    /**
+     * Publish the given LocationAvailables using the LocationAvailable client template in a transaction
+     *
+     * @param group group
+     * @param locationsAvailable messages to publish
+     */
+    public void publishLocationsAvailable(Group group, List<LocationAvailable> locationsAvailable)
+    {
+        publishMessages(Clients.locationAvailableClient, group, locationsAvailable);
+    }
+
+    /**
+     * Publish the given UserCreateds using the UserCreated client template in a transaction
+     *
+     * @param group group
+     * @param usersCreated messages to publish
+     */
+    public void publishUsersCreated(Group group, List<UserCreated> usersCreated)
+    {
+        publishMessages(Clients.userCreatedClient, group, usersCreated);
+    }
+
+    private <T extends Message> void publishMessage(TypedModeledFramework2<T, Group, Priority> typedClient, Group group, T message)
+    {
+        ModeledFramework<T> resolvedClient = typedClient.resolved(client, group, message.getPriority());
+        resolvedClient.set(message).exceptionally(e -> {
+            log.error("Could not publish message: " + message, e);
+            return null;
+        });
+    }
+
+    private <T extends Message> void publishMessages(TypedModeledFramework2<T, Group, Priority> typedClient, Group group, List<T> messages)
+    {
+        List<CuratorOp> operations = messages.stream()
+            .map(message -> typedClient
+                    .resolved(client, group, message.getPriority())
+                    .createOp(message)
+                )
+            .collect(Collectors.toList());
+        client.transaction().forOperations(operations).exceptionally(e -> {
+            log.error("Could not publish messages: " + messages, e);
+            return null;
+        });
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/README.md
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/README.md b/curator-examples/src/main/java/pubsub/README.md
new file mode 100644
index 0000000..f6d5971
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/README.md
@@ -0,0 +1,93 @@
+# Pub-Sub Example
+This example models a publish and subscribe system (note: it is not meant for production) using 
+the strongly typed modeled APIs in Apache Curator. 
+
+## Design Notes
+
+In this example, there are three models that can be published: `Instance`, `LocationAvailable` 
+and `UserCreated`. Instances have an `InstanceType`; LocationAvailable and UserCreated both have 
+a `Priority` and are associated with a `Group`. (Note: these names/objects are meant for 
+illustrative purposes only and are completely contrived)
+
+Each model is stored at a unique path in ZooKeeper:
+
+* Instance: `/root/pubsub/instances/TYPE/ID`
+* LocationAvailable: `/root/pubsub/messages/locations/GROUP/PRIORITY/ID`
+* UserCreated: `/root/pubsub/messages/users/GROUP/PRIORITY/ID`
+
+All models are stored using a TTL so that they automatically get deleted after 10 minutes.
+
+## Clients
+
+This example uses the "typed" models (`TypedModelSpec`, etc.). The typed paths, models and 
+clients are meant to be created early in your application and re-used as needed. Thus, you 
+can model your ZooKeeper usage and the rest of your application can use them without worrying 
+about correct paths, types, etc.
+
+`TypedModeledFramework` is a template that produces a `ModeledFramework` by applying 
+parameters to the `TypedZPath` in the contained `TypedModelSpec`. Curator provides variants 
+that accept from 1 to 10 parameters (`TypedModeledFramework`, `TypedModeledFramework2`, 
+`TypedModeledFramework3`, etc.).
+
+In this example, the TypedModeledFrameworks are defined in `Clients.java`. E.g.
+
+```
+public static final TypedModeledFramework2<LocationAvailable, Group, Priority> locationAvailableClient = 
+    TypedModeledFramework2.from(
+        ModeledFramework.builder(),
+        builder(LocationAvailable.class),
+        "/root/pubsub/messages/locations/{group}/{priority}"
+    );
+```
+
+## Publisher
+
+`Publisher.java` shows how to use the ModeledFramework to write models. There are methods to 
+write single instances and to write lists of instances in a transaction. Each publish method 
+resolves the appropriate typed client and then calls its `set()` method with the given model.
+
+## Subscriber
+
+`Subscriber.java` uses CachedModeledFrameworks to listen for changes on the parent nodes for 
+all of the models in this example. Each of the methods resolves the appropriate typed client 
+and then starts the cache (via `cached()`).
+
+## SubPubTest
+
+`SubPubTest.java` is a class that exercises this example. 
+
+* `start()` uses `Subscriber` to start a `CachedModeledFramework` for each combination of 
+the Instance + InstanceType, LocationAvailable + Group + Priority, and UserCreated + Group + Priority. It then adds a simple listener to each cache that merely prints the class name 
+and path whenever an update occurs (see `generalListener()`).
+* `start()` also starts a scheduled task that runs every second. This task calls 
+`publishSomething()`
+* `publishSomething()` randomly publishes either a single Instance, LocationAvailable, 
+UserCreated or a list of those.
+
+`SubPubTest.java` has a `main()` method. When you run you should see something similar to this:
+
+```
+Publishing 9 instances
+Subscribed Instance @ /root/pubsub/instances/proxy/1
+Subscribed Instance @ /root/pubsub/instances/web/2
+Subscribed Instance @ /root/pubsub/instances/cache/4
+Subscribed Instance @ /root/pubsub/instances/proxy/9
+Subscribed Instance @ /root/pubsub/instances/database/3
+Subscribed Instance @ /root/pubsub/instances/cache/5
+Subscribed Instance @ /root/pubsub/instances/database/6
+Subscribed Instance @ /root/pubsub/instances/cache/7
+Subscribed Instance @ /root/pubsub/instances/cache/8
+Publishing 1 userCreated
+Subscribed UserCreated @ /root/pubsub/messages/users/main/high/10
+Publishing 9 locationsAvailable
+Subscribed LocationAvailable @ /root/pubsub/messages/locations/admin/low/11
+Subscribed LocationAvailable @ /root/pubsub/messages/locations/admin/medium/12
+Subscribed LocationAvailable @ /root/pubsub/messages/locations/admin/medium/13
+Subscribed LocationAvailable @ /root/pubsub/messages/locations/admin/medium/14
+Subscribed LocationAvailable @ /root/pubsub/messages/locations/admin/medium/16
+Subscribed LocationAvailable @ /root/pubsub/messages/locations/admin/high/15
+Subscribed LocationAvailable @ /root/pubsub/messages/locations/admin/medium/17
+...
+```
+
+It runs for 1 minute and then exits.

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/SubPubTest.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/SubPubTest.java b/curator-examples/src/main/java/pubsub/SubPubTest.java
new file mode 100644
index 0000000..354d568
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/SubPubTest.java
@@ -0,0 +1,220 @@
+/**
+ * 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;
+
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.RetryOneTime;
+import org.apache.curator.test.TestingServer;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.cached.CachedModeledFramework;
+import org.apache.curator.x.async.modeled.cached.ModeledCacheListener;
+import pubsub.messages.LocationAvailable;
+import pubsub.messages.UserCreated;
+import pubsub.models.Group;
+import pubsub.models.Instance;
+import pubsub.models.InstanceType;
+import pubsub.models.Priority;
+import java.io.Closeable;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class SubPubTest implements Closeable
+{
+    private final TestingServer testingServer;
+    private final AsyncCuratorFramework client;
+    private final ScheduledExecutorService executorService;
+    private final List<CachedModeledFramework<Instance>> instanceSubscribers = new ArrayList<>();
+    private final List<CachedModeledFramework<LocationAvailable>> locationAvailableSubscribers = new ArrayList<>();
+    private final List<CachedModeledFramework<UserCreated>> userCreatedSubscribers = new ArrayList<>();
+
+    private static final AtomicLong nextId = new AtomicLong(1);
+
+    // arrays of random values used for this example
+    private static final Group[] groups = {new Group("main"), new Group("admin")};
+    private static final String[] hostnames = {"host1", "host2", "host3"};
+    private static final Integer[] ports = {80, 443, 9999};
+    private static final String[] locations = {"dc1", "dc2", "eu", "us"};
+    private static final Duration[] durations = {Duration.ofSeconds(1), Duration.ofMinutes(1), Duration.ofHours(1)};
+    private static final String[] positions = {"worker", "manager", "executive"};
+
+    public static void main(String[] args)
+    {
+        try ( SubPubTest subPubTest = new SubPubTest() )
+        {
+            subPubTest.start();
+            TimeUnit.MINUTES.sleep(1);  // run the test for a minute then exit
+        }
+        catch ( Exception e )
+        {
+            e.printStackTrace();
+        }
+    }
+
+    public SubPubTest() throws Exception
+    {
+        this.testingServer = new TestingServer();
+        client = AsyncCuratorFramework.wrap(CuratorFrameworkFactory.newClient(testingServer.getConnectString(), new RetryOneTime(1)));
+        executorService = Executors.newSingleThreadScheduledExecutor();
+    }
+
+    public void start()
+    {
+        client.unwrap().start();
+
+        Publisher publisher = new Publisher(client);
+        Subscriber subscriber = new Subscriber(client);
+
+        // start a subscriber/cache for Instances of each InstanceType
+        instanceSubscribers.addAll(
+            Arrays.stream(InstanceType.values())
+            .map(subscriber::startInstanceSubscriber)
+            .collect(Collectors.toList())
+        );
+
+        // start a subscriber/cache for LocationAvailables of each combination of Group and Priority
+        locationAvailableSubscribers.addAll(
+            Arrays.stream(Priority.values())
+                .flatMap(priority -> Arrays.stream(groups).map(group -> subscriber.startLocationAvailableSubscriber(group, priority)))
+                .collect(Collectors.toList())
+        );
+
+        // start a subscriber/cache for UserCreateds of each combination of Group and Priority
+        userCreatedSubscribers.addAll(
+            Arrays.stream(Priority.values())
+                .flatMap(priority -> Arrays.stream(groups).map(group -> subscriber.startUserCreatedSubscriber(group, priority)))
+                .collect(Collectors.toList())
+        );
+
+        // add listeners for each of the caches
+        instanceSubscribers.forEach(s -> s.listenable().addListener(generalListener()));
+        locationAvailableSubscribers.forEach(s -> s.listenable().addListener(generalListener()));
+        userCreatedSubscribers.forEach(s -> s.listenable().addListener(generalListener()));
+
+        // schedule the publisher task once a second
+        executorService.scheduleAtFixedRate(() -> publishSomething(publisher), 1, 1, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void close() throws IOException
+    {
+        executorService.shutdownNow();
+        try
+        {
+            executorService.awaitTermination(5, TimeUnit.SECONDS);
+        }
+        catch ( InterruptedException ignore )
+        {
+            Thread.currentThread().interrupt();
+        }
+
+        userCreatedSubscribers.forEach(CachedModeledFramework::close);
+        locationAvailableSubscribers.forEach(CachedModeledFramework::close);
+        instanceSubscribers.forEach(CachedModeledFramework::close);
+        client.unwrap().close();
+        testingServer.close();
+    }
+
+    private void publishSomething(Publisher publisher)
+    {
+        // randomly do some publishing - either single items or lists of items in a transaction
+        switch ( ThreadLocalRandom.current().nextInt(6) )
+        {
+            case 0:
+            {
+                Instance instance = new Instance(nextId(), random(InstanceType.values()), random(hostnames), random(ports));
+                System.out.println("Publishing 1 instance");
+                publisher.publishInstance(instance);
+                break;
+            }
+
+            case 1:
+            {
+                List<Instance> instances =  IntStream.range(1, 10)
+                    .mapToObj(__ -> new Instance(nextId(), random(InstanceType.values()), random(hostnames), random(ports)))
+                    .collect(Collectors.toList());
+                System.out.println(String.format("Publishing %d instances", instances.size()));
+                publisher.publishInstances(instances);
+                break;
+            }
+
+            case 2:
+            {
+                LocationAvailable locationAvailable = new LocationAvailable(nextId(), random(Priority.values()), random(locations), random(durations));
+                System.out.println("Publishing 1 locationAvailable");
+                publisher.publishLocationAvailable(random(groups), locationAvailable);
+                break;
+            }
+
+            case 3:
+            {
+                List<LocationAvailable> locationsAvailable =  IntStream.range(1, 10)
+                    .mapToObj(__ -> new LocationAvailable(nextId(), random(Priority.values()), random(locations), random(durations)))
+                    .collect(Collectors.toList());
+                System.out.println(String.format("Publishing %d locationsAvailable", locationsAvailable.size()));
+                publisher.publishLocationsAvailable(random(groups), locationsAvailable);
+                break;
+            }
+
+            case 4:
+            {
+                UserCreated userCreated = new UserCreated(nextId(), random(Priority.values()), random(locations), random(positions));
+                System.out.println("Publishing 1 userCreated");
+                publisher.publishUserCreated(random(groups), userCreated);
+                break;
+            }
+
+            case 5:
+            {
+                List<UserCreated> usersCreated =  IntStream.range(1, 10)
+                    .mapToObj(__ -> new UserCreated(nextId(), random(Priority.values()), random(locations), random(positions)))
+                    .collect(Collectors.toList());
+                System.out.println(String.format("Publishing %d usersCreated", usersCreated.size()));
+                publisher.publishUsersCreated(random(groups), usersCreated);
+                break;
+            }
+        }
+    }
+
+    private <T> ModeledCacheListener<T> generalListener()
+    {
+        return (type, path, stat, model) -> System.out.println(String.format("Subscribed %s @ %s", model.getClass().getSimpleName(), path));
+    }
+
+    @SafeVarargs
+    private final <T> T random(T... tab)
+    {
+        int index = ThreadLocalRandom.current().nextInt(tab.length);
+        return tab[index];
+    }
+
+    private String nextId()
+    {
+        return Long.toString(nextId.getAndIncrement());
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/Subscriber.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/Subscriber.java b/curator-examples/src/main/java/pubsub/Subscriber.java
new file mode 100644
index 0000000..94a6247
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/Subscriber.java
@@ -0,0 +1,84 @@
+/**
+ * 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;
+
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.modeled.cached.CachedModeledFramework;
+import org.apache.curator.x.async.modeled.typed.TypedModeledFramework2;
+import pubsub.messages.LocationAvailable;
+import pubsub.messages.UserCreated;
+import pubsub.models.Group;
+import pubsub.models.Instance;
+import pubsub.models.InstanceType;
+import pubsub.models.Message;
+import pubsub.models.Priority;
+
+public class Subscriber
+{
+    private final AsyncCuratorFramework client;
+
+    public Subscriber(AsyncCuratorFramework client)
+    {
+        this.client = client;
+    }
+
+    /**
+     * Start a subscriber (a CachedModeledFramework instance) using the LocationAvailable client template
+     *
+     * @param group group to listen for
+     * @param priority priority to listen for
+     * @return CachedModeledFramework instance (already started)
+     */
+    public CachedModeledFramework<LocationAvailable> startLocationAvailableSubscriber(Group group, Priority priority)
+    {
+        return startSubscriber(Clients.locationAvailableClient, group, priority);
+    }
+
+    /**
+     * Start a subscriber (a CachedModeledFramework instance) using the UserCreated client template
+     *
+     * @param group group to listen for
+     * @param priority priority to listen for
+     * @return CachedModeledFramework instance (already started)
+     */
+    public CachedModeledFramework<UserCreated> startUserCreatedSubscriber(Group group, Priority priority)
+    {
+        return startSubscriber(Clients.userCreatedClient, group, priority);
+    }
+
+    /**
+     * Start a subscriber (a CachedModeledFramework instance) using the Instance client template
+     *
+     * @param instanceType type to listen for
+     * @return CachedModeledFramework instance (already started)
+     */
+    public CachedModeledFramework<Instance> startInstanceSubscriber(InstanceType instanceType)
+    {
+        CachedModeledFramework<Instance> resolved = Clients.instanceClient.resolved(client, instanceType).cached();
+        resolved.start();
+        return resolved;
+    }
+
+    private <T extends Message> CachedModeledFramework<T> startSubscriber(TypedModeledFramework2<T, Group, Priority> typedClient, Group group, Priority priority)
+    {
+        CachedModeledFramework<T> resolved = typedClient.resolved(client, group, priority).cached();
+        resolved.start();
+        return resolved;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/messages/LocationAvailable.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/messages/LocationAvailable.java b/curator-examples/src/main/java/pubsub/messages/LocationAvailable.java
new file mode 100644
index 0000000..dd90107
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/messages/LocationAvailable.java
@@ -0,0 +1,55 @@
+/**
+ * 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.messages;
+
+import pubsub.models.Message;
+import pubsub.models.Priority;
+import java.time.Duration;
+import java.util.Objects;
+
+public class LocationAvailable extends Message
+{
+    private final String name;
+    private final Duration availableUntil;
+
+    public LocationAvailable()
+    {
+        this(Priority.low, "", Duration.ZERO);
+    }
+
+    public LocationAvailable(Priority priority, String name, Duration availableUntil)
+    {
+        super(priority);
+        this.name = Objects.requireNonNull(name, "name cannot be null");
+        this.availableUntil = Objects.requireNonNull(availableUntil, "availableUntil cannot be null");
+    }
+
+    public LocationAvailable(String id, Priority priority, String name, Duration availableUntil)
+    {
+        super(id, priority);
+        this.name = Objects.requireNonNull(name, "name cannot be null");
+        this.availableUntil = Objects.requireNonNull(availableUntil, "availableUntil cannot be null");
+    }
+
+    @Override
+    public String toString()
+    {
+        return "LocationAvailable{" + "name='" + name + '\'' + ", availableUntil=" + availableUntil + "} " + super.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/messages/UserCreated.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/messages/UserCreated.java b/curator-examples/src/main/java/pubsub/messages/UserCreated.java
new file mode 100644
index 0000000..bf753a8
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/messages/UserCreated.java
@@ -0,0 +1,64 @@
+/**
+ * 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.messages;
+
+import pubsub.models.Message;
+import pubsub.models.Priority;
+import java.util.Objects;
+
+public class UserCreated extends Message
+{
+    private final String name;
+    private final String position;
+
+    public UserCreated()
+    {
+        this(Priority.low, "","");
+    }
+
+    public UserCreated(Priority priority, String name, String position)
+    {
+        super(priority);
+        this.name = Objects.requireNonNull(name, "name cannot be null");
+        this.position = Objects.requireNonNull(position, "position cannot be null");
+    }
+
+    public UserCreated(String id, Priority priority, String name, String position)
+    {
+        super(id, priority);
+        this.name = Objects.requireNonNull(name, "name cannot be null");
+        this.position = Objects.requireNonNull(position, "position cannot be null");
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public String getPosition()
+    {
+        return position;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "UserCreated{" + "name='" + name + '\'' + ", position='" + position + '\'' + "} " + super.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/models/Group.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/models/Group.java b/curator-examples/src/main/java/pubsub/models/Group.java
new file mode 100644
index 0000000..07d149f
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/models/Group.java
@@ -0,0 +1,47 @@
+/**
+ * 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;
+
+import org.apache.curator.x.async.modeled.NodeName;
+
+public class Group implements NodeName
+{
+    private final String groupName;
+
+    public Group()
+    {
+        this("");
+    }
+
+    public Group(String groupName)
+    {
+        this.groupName = groupName;
+    }
+
+    public String getGroupName()
+    {
+        return groupName;
+    }
+
+    @Override
+    public String nodeName()
+    {
+        return groupName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/models/Instance.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/models/Instance.java b/curator-examples/src/main/java/pubsub/models/Instance.java
new file mode 100644
index 0000000..981f113
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/models/Instance.java
@@ -0,0 +1,76 @@
+/**
+ * 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;
+
+import org.apache.curator.x.async.modeled.NodeName;
+import java.util.Objects;
+import java.util.UUID;
+
+public class Instance implements NodeName
+{
+    private final String id;
+    private final InstanceType type;
+    private final String hostname;
+    private final int port;
+
+    public Instance()
+    {
+        this(UUID.randomUUID().toString(), InstanceType.proxy, "", 0);
+    }
+
+    public Instance(String id, InstanceType type, String hostname, int port)
+    {
+        this.id = Objects.requireNonNull(id, "id cannot be null");
+        this.type = Objects.requireNonNull(type, "type cannot be null");
+        this.hostname = Objects.requireNonNull(hostname, "hostname cannot be null");
+        this.port = port;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public InstanceType getType()
+    {
+        return type;
+    }
+
+    public String getHostname()
+    {
+        return hostname;
+    }
+
+    public int getPort()
+    {
+        return port;
+    }
+
+    @Override
+    public String nodeName()
+    {
+        return id;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "Instance{" + "id='" + id + '\'' + ", type=" + type + ", hostname='" + hostname + '\'' + ", port=" + port + '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/models/InstanceType.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/models/InstanceType.java b/curator-examples/src/main/java/pubsub/models/InstanceType.java
new file mode 100644
index 0000000..176048c
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/models/InstanceType.java
@@ -0,0 +1,27 @@
+/**
+ * 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 InstanceType
+{
+    database,
+    cache,
+    web,
+    proxy
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-examples/src/main/java/pubsub/models/Message.java
----------------------------------------------------------------------
diff --git a/curator-examples/src/main/java/pubsub/models/Message.java b/curator-examples/src/main/java/pubsub/models/Message.java
new file mode 100644
index 0000000..7d92e99
--- /dev/null
+++ b/curator-examples/src/main/java/pubsub/models/Message.java
@@ -0,0 +1,67 @@
+/**
+ * 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;
+
+import org.apache.curator.x.async.modeled.NodeName;
+import java.util.Objects;
+import java.util.UUID;
+
+public abstract class Message implements NodeName
+{
+    private final String id;
+    private final Priority priority;
+
+    protected Message()
+    {
+        this(UUID.randomUUID().toString(), Priority.low);
+    }
+
+    protected Message(Priority priority)
+    {
+        this(UUID.randomUUID().toString(), priority);
+    }
+
+    protected Message(String id, Priority priority)
+    {
+        this.id = Objects.requireNonNull(id, "id cannot be null");
+        this.priority = Objects.requireNonNull(priority, "messageType cannot be null");
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    public Priority getPriority()
+    {
+        return priority;
+    }
+
+    @Override
+    public String nodeName()
+    {
+        return id;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "Message{" + "id='" + id + '\'' + ", priority=" + priority + '}';
+    }
+}


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

Posted by ra...@apache.org.
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();
+}


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

Posted by ra...@apache.org.
http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath7.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath7.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath7.java
new file mode 100644
index 0000000..a6eb4d3
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath7.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 7 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath7<T1, T2, T3, T4, T5, T6, T7>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7> TypedZPath7<T1, T2, T3, T4, T5, T6, T7> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7> TypedZPath7<T1, T2, T3, T4, T5, T6, T7> from(ZPath path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7) -> path.resolved(p1, p2, p3, p4, p5, p6, p7);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath8.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath8.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath8.java
new file mode 100644
index 0000000..68086b5
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath8.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 8 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath8<T1, T2, T3, T4, T5, T6, T7, T8>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7, T8 p8);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7, T8> TypedZPath8<T1, T2, T3, T4, T5, T6, T7, T8> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7, T8> TypedZPath8<T1, T2, T3, T4, T5, T6, T7, T8> from(ZPath path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7, p8) -> path.resolved(p1, p2, p3, p4, p5, p6, p7, p8);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath9.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath9.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath9.java
new file mode 100644
index 0000000..e03c1f1
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/typed/TypedZPath9.java
@@ -0,0 +1,52 @@
+/**
+ * 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.typed;
+
+import org.apache.curator.x.async.modeled.ZPath;
+
+/**
+ * Same as {@link org.apache.curator.x.async.modeled.typed.TypedZPath}, but with 9 parameters
+ */
+@FunctionalInterface
+public interface TypedZPath9<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+{
+    ZPath resolved(T1 p1, T2 p2, T3 p3, T4 p4, T5 p5, T6 p6, T7 p7, T8 p8, T9 p9);
+
+    /**
+     * Return a TypedZPath using {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     *
+     * @param pathWithIds path to pass to {@link org.apache.curator.x.async.modeled.ZPath#parseWithIds}
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7, T8, T9> TypedZPath9<T1, T2, T3, T4, T5, T6, T7, T8, T9> from(String pathWithIds)
+    {
+        return from(ZPath.parseWithIds(pathWithIds));
+    }
+
+    /**
+     * Return a TypedZPath
+     *
+     * @param path path to use
+     * @return TypedZPath
+     */
+    static <T1, T2, T3, T4, T5, T6, T7, T8, T9> TypedZPath9<T1, T2, T3, T4, T5, T6, T7, T8, T9> from(ZPath path)
+    {
+        return (p1, p2, p3, p4, p5, p6, p7, p8, p9) -> path.resolved(p1, p2, p3, p4, p5, p6, p7, p8, p9);
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/Versioned.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/Versioned.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/Versioned.java
new file mode 100644
index 0000000..0bd723b
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/Versioned.java
@@ -0,0 +1,69 @@
+/**
+ * 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.versioned;
+
+/**
+ * A container for a model instance and a version. Can be used with the
+ * {@link org.apache.curator.x.async.modeled.ModeledFramework#versioned()} APIs
+ */
+@FunctionalInterface
+public interface Versioned<T>
+{
+    /**
+     * Returns the contained model
+     *
+     * @return model
+     */
+    T model();
+
+    /**
+     * Returns the version of the model when it was read
+     *
+     * @return version
+     */
+    default int version()
+    {
+        return -1;
+    }
+
+    /**
+     * Return a new Versioned wrapper for the given model and version
+     *
+     * @param model model
+     * @param version version
+     * @return new Versioned wrapper
+     */
+    static <T> Versioned<T> from(T model, int version)
+    {
+        return new Versioned<T>()
+        {
+            @Override
+            public int version()
+            {
+                return version;
+            }
+
+            @Override
+            public T model()
+            {
+                return model;
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/VersionedModeledFramework.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/VersionedModeledFramework.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/VersionedModeledFramework.java
new file mode 100644
index 0000000..c725fd3
--- /dev/null
+++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/versioned/VersionedModeledFramework.java
@@ -0,0 +1,56 @@
+/**
+ * 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.versioned;
+
+import org.apache.curator.framework.api.transaction.CuratorOp;
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.zookeeper.data.Stat;
+
+public interface VersionedModeledFramework<T>
+{
+    /**
+     * @see org.apache.curator.x.async.modeled.ModeledFramework#set(Object)
+     */
+    AsyncStage<String> set(Versioned<T> model);
+
+    /**
+     * @see org.apache.curator.x.async.modeled.ModeledFramework#set(Object, org.apache.zookeeper.data.Stat)
+     */
+    AsyncStage<String> set(Versioned<T> model, Stat storingStatIn);
+
+    /**
+     * @see org.apache.curator.x.async.modeled.ModeledFramework#read()
+     */
+    AsyncStage<Versioned<T>> read();
+
+    /**
+     * @see org.apache.curator.x.async.modeled.ModeledFramework#read(org.apache.zookeeper.data.Stat)
+     */
+    AsyncStage<Versioned<T>> read(Stat storingStatIn);
+
+    /**
+     * @see org.apache.curator.x.async.modeled.ModeledFramework#updateOp(Object)
+     */
+    AsyncStage<Stat> update(Versioned<T> model);
+
+    /**
+     * @see org.apache.curator.x.async.modeled.ModeledFramework#updateOp(Object)
+     */
+    CuratorOp updateOp(Versioned<T> model);
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/site/confluence/async.confluence
----------------------------------------------------------------------
diff --git a/curator-x-async/src/site/confluence/async.confluence b/curator-x-async/src/site/confluence/async.confluence
new file mode 100644
index 0000000..619f5c8
--- /dev/null
+++ b/curator-x-async/src/site/confluence/async.confluence
@@ -0,0 +1,212 @@
+h1. Curator Async
+
+With this DSL you can do asynchronous tasks in a more natural, functional way using
+[Java 8 lambdas|https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html]. For example:
+
+{code}
+// let "client" be a CuratorFramework instance
+AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);
+async.checkExists().forPath(somePath).thenAccept(stat -> mySuccessOperation(stat));
+{code}
+
+h2. Usage
+
+Note: To use Curator Async, you should be familiar with Java 8's lambdas, CompletedFuture and CompletionStage.
+
+Create a [[CuratorFramework|../curator\-framework/index.html]] instance in the normal way. You then wrap this instance using
+AsyncCuratorFramework. i.e.
+
+{code}
+// let "client" be a CuratorFramework instance
+AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);
+{code}
+
+AsyncCuratorFramework has most of the same builder methods that CuratorFramework does with some important
+differences:
+
+* AsyncCuratorFramework builders return {{AsyncStage}} instances
+* AsyncCuratorFramework builders have no checked exceptions
+* Many of the builder methods have been simplified/clarified
+* All builders invoke the asynchronous versions of ZooKeeper APIs
+* Watchers also use CompletionStages \- see below for details
+
+h4. AsyncStage
+
+AsyncStage instances extend Java 8's CompletionStage. CompletionStage objects can be "completed" with a success
+value or an exception. The parameterized type of the AsyncStage will
+be whatever the builder used would naturally return as a success value. E.g. the async getData() builder's AsyncStage is
+parameterized with "byte\[\]".
+
+h4. Watchers
+
+ZooKeeper watchers also get the CompletionStage treatment in Curator Async. To add a watcher, call
+watched() prior to starting the appropriate builders. E.g.
+
+{code}
+async.watched().getData().forPath(path) ...
+{code}
+
+Thus, a data watcher will be set on the specified path. You access the CompletionStage for the watcher
+by using the event() method of AsyncStage. Here is a complete example:
+
+{code}
+async.watched().getData().forPath(path).event().thenAccept(watchedEvent -> watchWasTriggered(watchedEvent));
+{code}
+
+ZooKeeper calls watchers when there is a connection loss. This can make using the CompletionStage
+somewhat complicated (see AsyncEventException below). If you are not interested in watcher connection
+problems, you can tell Curator Async to not send them by calling:
+
+{code}
+// only complete the CompletionStage when the watcher is successfully triggered
+// i.e. don't complete on connection issues
+async.with(WatchMode.successOnly).watched()...
+{code}
+
+h4. AsyncEventException
+
+When an async watcher fails the exception set in the CompletionStage will be of type {{AsyncEventException}}.
+This exception allows you to see the KeeperState that caused the trigger and allows you to reset the
+completion stage. Reset is needed because ZooKeeper temporarily triggers watchers when there is a connection
+event (unless {{WatchMode.successOnly}} is used). However, the watcher stays set for the original operation. Use {{AsyncEventException#reset}}
+to start a new completion stage that will wait on the next trigger of the watcher.
+
+E.g.
+
+{code}
+AsyncStage stage = ...
+stage.event().exceptionally(e -> {
+    AsyncEventException asyncEx = (AsyncEventException)e;
+
+    ... note a connection problem ...
+
+    asyncEx.reset().thenAccept(watchedEvent -> watchWasTriggered(watchedEvent));
+});
+{code}
+
+h4. AsyncResult
+
+As a convenience, you can use {{AsyncResult}} to combine ZooKeeper method value, the ZooKeeper result
+code and any exception in one object allowing you to not worry about exceptional completions. i.e. the {{CompletionStage}}
+returned by {{AsyncResult.of()}} always completes successfully with an AsyncResult object.
+
+AsyncResult has methods to get either the method result (a path, Stat, etc.), a KeeperException code
+or a general exception:
+
+{code}
+Optional<T> getValue();
+
+KeeperException.Code getCode();
+
+Optional<Throwable> getException();
+{code}
+
+Use AsyncResult by wrapping an {{AsyncStage}} value. i.e.
+
+{code}
+CompletionStage<AsyncResult<Stat>> resultStage = AsyncResult.of(async.checkExists().forPath(path));
+resultStage.thenAccept(result -> {
+    if ( result.getValue().isPresent() ) {
+        // ...
+    } else if ( result.getCode() == KeeperException.Code.NOAUTH ) {
+        // ...
+    }
+    // etc.
+});
+{code}
+
+h2. Examples
+
+h4. Create a sequential ZNode
+
+Create a sequential ZNode and, once successfully completed, set a watcher
+on the ZNode. Note: this code does not deal with errors. Should a connection
+problem occur or another exception occur, the completion lambda will never be called.
+
+{code}
+async.create().withMode(PERSISTENT_SEQUENTIAL).forPath(path).thenAccept(actualPath ->
+    async.watched().getData().forPath(actualPath).thenApply(() -> watchTriggered()));
+{code}
+
+----
+
+h4. AsyncStage canonical usage
+
+This is the canonical way to deal with AsyncStage. Use the handle() method which provides
+both the success value and the exception. The exception will be non\-null on error.
+
+{code}
+async.create().withOptions(EnumSet.of(doProtected)).forPath(path).handle((actualPath, exception) -> {
+    if ( exception != null )
+    {
+        // handle problem
+    }
+    else
+    {
+        // actualPath is the path created
+    }
+    return null;
+});
+{code}
+
+----
+
+h4. Simplified usage via AsyncResult
+
+{code}
+AsyncResult.of(async.create().withOptions(EnumSet.of(doProtected)).forPath(path)).thenAccept(result -> {
+    if ( result.getRawValue() != null )
+    {
+        // result.getRawValue() is the path created
+    }
+    else
+    {
+        // ...
+    }
+});
+{code}
+
+----
+
+h4. Using executors
+
+Your completion routines can operate in a separate thread if you provide an executor.
+
+{code}
+async.create().withOptions(EnumSet.of(createParentsIfNeeded)).forPath("/a/b/c")
+    .thenAcceptAsync(path -> handleCreate(path), executor);
+{code}
+
+----
+
+h4. Separate handlers
+
+This example shows specifying separate completion handlers for success and exception.
+
+{code}
+AsyncStage<byte[]> stage = async.getData().forPath("/my/path");
+stage.exceptionally(e -> {
+    if ( e instanceof KeeperException.NoNodeException )
+    {
+        // handle no node
+    }
+    else
+    {
+        // handle other
+    }
+    return null;
+});
+stage.thenAccept(data -> processData(data));
+{code}
+
+----
+
+h4. Synchronous usage
+
+CompletionStage provides a blocking method as well so that you can block to get the result
+of an operation. i.e. this makes it possible to use the async APIs in a synchronous way.
+
+{code}
+// NOTE: get() specifies a checked exception
+async.create().forPath("/foo").toCompletableFuture().get();
+{code}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/site/confluence/index.confluence
----------------------------------------------------------------------
diff --git a/curator-x-async/src/site/confluence/index.confluence b/curator-x-async/src/site/confluence/index.confluence
index 34b6c36..4d81d44 100644
--- a/curator-x-async/src/site/confluence/index.confluence
+++ b/curator-x-async/src/site/confluence/index.confluence
@@ -4,7 +4,7 @@ h2. Packaging
 
 Curator Async is in its own package in Maven Central: curator\-x\-async
 
-h2. What Is a Curator Async?
+h2. What Is Curator Async?
 
 Curator Async is a [DSL|https://en.wikipedia.org/wiki/Domain-specific_language] that wraps existing
 {{CuratorFramework}} instances. This DSL is entirely asynchronous and uses
@@ -13,212 +13,39 @@ mechanism for chaining, composing, etc. Additionally, Curator's original DSL has
 and simplified, in particular for operations such as {{create()}}.
 
 With this DSL you can do asynchronous tasks in a more natural, functional way using
-[Java 8 lambdas|https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html]. For example:
-
-{code}
-// let "client" be a CuratorFramework instance
-AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);
-async.checkExists().forPath(somePath).thenAccept(stat -> mySuccessOperation(stat));
-{code}
+[Java 8 lambdas|https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html].
 
-h2. Usage
+The Curator Async package also contains a strongly typed DSL and strongly typed Cache Recipe wrappers that
+allows you to map a ZooKeeper path to a serializable class as opposed to raw byte arrays.
 
-Note: To use Curator Async, you should be familiar with Java 8's lambdas, CompletedFuture and CompletionStage.
+h2. [[Curator Async|async.html]]
 
-Create a [[CuratorFramework|../curator\-framework/index.html]] instance in the normal way. You then wrap this instance using
-AsyncCuratorFramework. i.e.
+With this DSL you can do asynchronous tasks in a more natural, functional way using
+[Java 8 lambdas|https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html]. For example:
 
 {code}
 // let "client" be a CuratorFramework instance
 AsyncCuratorFramework async = AsyncCuratorFramework.wrap(client);
+async.checkExists().forPath(somePath).thenAccept(stat -> mySuccessOperation(stat));
 {code}
 
-AsyncCuratorFramework has most of the same builder methods that CuratorFramework does with some important
-differences:
-
-* AsyncCuratorFramework builders return {{AsyncStage}} instances
-* AsyncCuratorFramework builders have no checked exceptions
-* Many of the builder methods have been simplified/clarified
-* All builders invoke the asynchronous versions of ZooKeeper APIs
-* Watchers also use CompletionStages \- see below for details
-
-h4. AsyncStage
-
-AsyncStage instances extend Java 8's CompletionStage. CompletionStage objects can be "completed" with a success
-value or an exception. The parameterized type of the AsyncStage will
-be whatever the builder used would naturally return as a success value. E.g. the async getData() builder's AsyncStage is
-parameterized with "byte\[\]".
-
-h4. Watchers
-
-ZooKeeper watchers also get the CompletionStage treatment in Curator Async. To add a watcher, call
-watched() prior to starting the appropriate builders. E.g.
-
-{code}
-async.watched().getData().forPath(path) ...
-{code}
-
-Thus, a data watcher will be set on the specified path. You access the CompletionStage for the watcher
-by using the event() method of AsyncStage. Here is a complete example:
-
-{code}
-async.watched().getData().forPath(path).event().thenAccept(watchedEvent -> watchWasTriggered(watchedEvent));
-{code}
-
-ZooKeeper calls watchers when there is a connection loss. This can make using the CompletionStage
-somewhat complicated (see AsyncEventException below). If you are not interested in watcher connection
-problems, you can tell Curator Async to not send them by calling:
-
-{code}
-// only complete the CompletionStage when the watcher is successfully triggered
-// i.e. don't complete on connection issues
-async.with(WatchMode.successOnly).watched()...
-{code}
-
-h4. AsyncEventException
-
-When an async watcher fails the exception set in the CompletionStage will be of type {{AsyncEventException}}.
-This exception allows you to see the KeeperState that caused the trigger and allows you to reset the
-completion stage. Reset is needed because ZooKeeper temporarily triggers watchers when there is a connection
-event (unless {{WatchMode.successOnly}} is used). However, the watcher stays set for the original operation. Use {{AsyncEventException#reset}}
-to start a new completion stage that will wait on the next trigger of the watcher.
-
-E.g.
-
-{code}
-AsyncStage stage = ...
-stage.event().exceptionally(e -> {
-    AsyncEventException asyncEx = (AsyncEventException)e;
-
-    ... note a connection problem ...
-
-    asyncEx.reset().thenAccept(watchedEvent -> watchWasTriggered(watchedEvent));
-});
-{code}
-
-h4. AsyncResult
-
-As a convenience, you can use {{AsyncResult}} to combine ZooKeeper method value, the ZooKeeper result
-code and any exception in one object allowing you to not worry about exceptional completions. i.e. the {{CompletionStage}}
-returned by {{AsyncResult.of()}} always completes successfully with an AsyncResult object.
-
-AsyncResult has methods to get either the method result (a path, Stat, etc.), a KeeperException code
-or a general exception:
-
-{code}
-Optional<T> getValue();
-
-KeeperException.Code getCode();
-
-Optional<Throwable> getException();
-{code}
-
-Use AsyncResult by wrapping an {{AsyncStage}} value. i.e.
-
-{code}
-CompletionStage<AsyncResult<Stat>> resultStage = AsyncResult.of(async.checkExists().forPath(path));
-resultStage.thenAccept(result -> {
-    if ( result.getValue().isPresent() ) {
-        // ...
-    } else if ( result.getCode() == KeeperException.Code.NOAUTH ) {
-        // ...
-    }
-    // etc.
-});
-{code}
-
-h2. Examples
-
-h4. Create a sequential ZNode
-
-Create a sequential ZNode and, once successfully completed, set a watcher
-on the ZNode. Note: this code does not deal with errors. Should a connection
-problem occur or another exception occur, the completion lambda will never be called.
-
-{code}
-async.create().withMode(PERSISTENT_SEQUENTIAL).forPath(path).thenAccept(actualPath ->
-    async.watched().getData().forPath(actualPath).thenApply(() -> watchTriggered()));
-{code}
-
-----
-
-h4. AsyncStage canonical usage
-
-This is the canonical way to deal with AsyncStage. Use the handle() method which provides
-both the success value and the exception. The exception will be non\-null on error.
-
-{code}
-async.create().withOptions(EnumSet.of(doProtected)).forPath(path).handle((actualPath, exception) -> {
-    if ( exception != null )
-    {
-        // handle problem
-    }
-    else
-    {
-        // actualPath is the path created
-    }
-    return null;
-});
-{code}
-
-----
-
-h4. Simplified usage via AsyncResult
-
-{code}
-AsyncResult.of(async.create().withOptions(EnumSet.of(doProtected)).forPath(path)).thenAccept(result -> {
-    if ( result.getRawValue() != null )
-    {
-        // result.getRawValue() is the path created
-    }
-    else
-    {
-        // ...
-    }
-});
-{code}
-
-----
+See [[Curator Async|async.html]] for details.
 
-h4. Using executors
+h2. [[Modeled Curator|modeled.html]]
 
-Your completion routines can operate in a separate thread if you provide an executor.
+This is a strongly typed DSL that allows you to map a Curator\-style client to:
 
-{code}
-async.create().withOptions(EnumSet.of(createParentsIfNeeded)).forPath("/a/b/c")
-    .thenAcceptAsync(path -> handleCreate(path), executor);
-{code}
-
-----
+* A ZooKeeper path (supporting parameterized substitutions)
+* A serializer for the data stored at the path
+* Options for how nodes should be created (sequential, compressed data, ttl, etc.)
+* ACLs for the nodes at the path
+* Options for how to delete nodes (guaranteed, deleting children, etc.)
 
-h4. Separate handlers
-
-This example shows specifying separate completion handlers for success and exception.
+For example:
 
 {code}
-AsyncStage<byte[]> stage = async.getData().forPath("/my/path");
-stage.exceptionally(e -> {
-    if ( e instanceof KeeperException.NoNodeException )
-    {
-        // handle no node
-    }
-    else
-    {
-        // handle other
-    }
-    return null;
-});
-stage.thenAccept(data -> processData(data));
+ModeledFramework<Foo> modeled = ModeledFramework.wrap(client, fooModelSpec);
+modeled.set(new Foo());
 {code}
 
-----
-
-h4. Synchronous usage
-
-CompletionStage provides a blocking method as well so that you can block to get the result
-of an operation. i.e. this makes it possible to use the async APIs in a synchronous way.
-
-{code}
-// NOTE: get() specifies a checked exception
-async.create().forPath("/foo").toCompletableFuture().get();
-{code}
+See [[Modeled Curator|modeled.html]] for details.

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/site/confluence/modeled-components.confluence
----------------------------------------------------------------------
diff --git a/curator-x-async/src/site/confluence/modeled-components.confluence b/curator-x-async/src/site/confluence/modeled-components.confluence
new file mode 100644
index 0000000..62bcdeb
--- /dev/null
+++ b/curator-x-async/src/site/confluence/modeled-components.confluence
@@ -0,0 +1,186 @@
+h1. Modeled Curator \- Components
+
+Modeled Curator components are intended to allow you to model your ZooKeeper usage early in your application
+so that the majority of the code that interacts with ZooKeeper doesn't need to be concerned with
+paths, byte arrays, ACLs, options, etc. The [[Pub\-Sub Example|https://github.com/apache/curator/tree/master/curator-examples/src/main/java/pubsub]]
+can give you some ideas on how to accomplish this.
+
+h2. ZPath
+
+Instead of using raw string paths, Modeled Curator defines the {{ZPath}} interface that abstracts
+ZooKeeper paths. ZPaths can be simple static paths or can contain parameters that can be replaced
+as needed.
+
+To build a simple static path, use:
+
+{code}
+ZPath path = ZPath.parse("/my/static/path");
+{code}
+
+To build a path with parameters, use. {{ZPath.parseWithIds()}} using the value "\{XXXX\}" to
+denote a parameter. You can then use the {{resolve()}} method to replace the parameters. The value
+between "\{\}" can be any value. E.g.
+
+{code}
+ZPath path = ZPath.parseWithIds("/foo/{first param}/bar/{second param}");
+
+...
+
+ZPath resolvedPath = path.resolve(param1, param2);
+{code}
+
+h3. NodeName
+
+Parameters are resolved by calling {{toString()}} on the parameter. You can use {{NodeName}}
+to change this behavior. If a parameter implements {{NodeName}} the {{nodeName()}} method
+is used as the parameter value.
+
+h3. Partial Resolution
+
+Note: ZPaths can be partially resolved. E.g.
+
+{code}
+ZPath path = ZPath.parseWithIds("/foo/{type}/bar/{id}");
+
+...
+
+ZPath partial = path.resolve("standard");
+// partial is now "/foo/standard/bar/{id}"
+{code}
+
+ModeledFramework takes advantage of this. [[See below|#ModeledFramework]] for details.
+
+h2. ModelSpec
+
+A {{ModelSpec}} contains all the metadata needed to operate on a ZooKeeper path:
+
+* A ZPath
+* A serializer for the data stored at the path
+* Options for how nodes should be created (sequential, compressed data, ttl, etc.)
+* ACLs for the nodes at the path
+* Options for how to delete nodes (guaranteed, deleting children, etc.)
+
+ModelSpec instances are created via a builder. The builder sets defaults that should be
+useful for most applications but you can alter any of these as needed.
+
+{code}
+// a standard model spec for the given path and serializer
+// the model spec will have no ACLs and the options:
+// * createParentsAsContainers
+// * setDataIfExists
+// * DeleteOption.guaranteed
+ModelSpec<MyModel> spec = ModelSpec.builder(path, JacksonModelSerializer.build(MyModel.class)).build();
+{code}
+
+As a convenience, ModelSpec provides {{resolve()}} methods in case the ZPath used has parameters.
+E.g.
+
+{code}
+ZPath path = ZPath.parseWithIds("/foo/{id}/bar/{id}");
+ModelSpec<MyModel> spec = ModelSpec.builder(path, JacksonModelSerializer.build(MyModel.class)).build();
+
+...
+
+ModelSpec<MyModel> resolvedSpec = spec.resolve(param1, param2);
+{code}
+
+h3. JacksonModelSerializer
+
+A Jackson serializer, {{JacksonModelSerializer}}, is included. However, the Jackson dependency for it is
+specified as "provided" 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.
+
+E.g. for Maven:
+
+{code}
+<dependency>
+    <groupId>com.fasterxml.jackson.core</groupId>
+    <artifactId>jackson-databind</artifactId>
+    <version>XXXX</version>
+</dependency>
+{code}
+
+h2. ModeledFramework
+
+{{ModeledFramework}} ties together all the metadata into a Curator\-style instance that is
+used to perform ZooKeeper operations. E.g.
+
+{code}
+ModeledFramework<MyModel> modeledClient = ModeledFramework.wrap(client, myModelSpec);
+
+...
+
+MyModel instance = ...
+modeledClient.set(instance);
+{code}
+
+The "set" call in the above example is the equivalent of:
+
+{code}
+MyModel instance = ...
+String path = "/foo/bar/" + instance.getId();
+byte[] data = serializer.serialize(instance);
+client.create()
+    .withOptions(Sets.newHashSet(CreateOption.createParentsAsContainers, CreateOption.setDataIfExists))
+    .forPath(path, data);
+{code}
+
+To get a value:
+
+{code}
+ModeledFramework<MyModel> modeledClient = ModeledFramework.wrap(client, myModelSpec);
+
+...
+
+modeledClient.read().whenComplete((value, e) -> {
+    if ( e != null ) {
+        // handle the error
+    } else {
+        // "value" is the MyModel instance
+    }
+});
+{code}
+
+The "read" call in the above example is the equivalent of:
+
+{code}
+String path = "/foo/bar/" + instanceId;
+client.getData().forPath(path).whenComplete((data, e) -> {
+    if ( e != null ) {
+        // handle the error
+    } else {
+        // NOTE: you must deal with possible deserialization problems
+        // caused by clients that write bad data
+        // If all of your code uses ModeledFramework you can guarantee that
+        // the data is always correctly written
+        MyModel model = serializer.deserialize(data);
+        // ...
+    }
+});
+{code}
+
+h3. Partially Resolved ZPaths and Set/Update
+
+ModeledFramework's various {{set}} and {{update}} methods check for unresolved ZPaths. If the current
+modelSpec has an unresolved ZPath when set/update is called, it is automatically resolved using the model
+instance being set/updated. E.g.
+
+{code}
+ZPath path = ZPath.parseWithIds("/root/{type}/instance/{id}");
+ModelSpec<MyModel> modelSpec = ModelSpec.builder(path, serializer);
+ModeledFramework<MyModel> modeledClient = ModeledFramework.wrap(modelSpec, client, modelSpec);
+
+...
+
+String currentType = ...
+MyModel model = ...
+modeledClient.resolved(currentType).set(model); // internally, ModeledFramework calls ZPath.resolved()
+                                                // using "model" as the argument to get the actual ZPath
+{code}
+
+h2. Caching and Typed Parameters
+
+In addition to the above features, Modeled Curator supports [[Integrated Caching|modeled-typed.html]],
+[[Typed Parameters|modeled-typed.html]] and [[Versioning|modeled-typed.html]]. See
+[[Caching and Typed Parameters|modeled-typed.html]] for details.
+

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/site/confluence/modeled-typed.confluence
----------------------------------------------------------------------
diff --git a/curator-x-async/src/site/confluence/modeled-typed.confluence b/curator-x-async/src/site/confluence/modeled-typed.confluence
new file mode 100644
index 0000000..c02ea80
--- /dev/null
+++ b/curator-x-async/src/site/confluence/modeled-typed.confluence
@@ -0,0 +1,89 @@
+h1. Modeled Curator \- Caching, Typed Parameters and Versioning
+
+In addition to its [[main features|modeled-components.html]] Modeled Curator also supports
+integrated caching, typed parameters and versioning.
+
+h2. Caching
+
+{{ModeledFramework}} instances can be wrapped with a facade that uses a Curator cache internally.
+All read operations use this cache instead of making direct ZooKeeper calls. You can also
+listen for node changes. E.g.
+
+{code}
+ModeledFramework<MyModel> modeledClient = ModeledFramework.wrap(client, myModelSpec);
+CachedModeledFramework<MyModel> cached = modeledClient.cached();
+cached.start();
+
+// reads come from the cache
+cached.read().whenComplete(...) ...
+
+cached.listenable.addListener((type, path, stat, model) -> {
+    // type is NODE_ADDED, NODE_UPDATED, etc.
+});
+{code}
+
+h3. Unresolved Paths and Caching
+
+If the last node in the ModelSpec's path is a parameter, CachedModeledFramework will automatically
+listen to the parent path. E.g.
+
+{code}
+ZPath path = ZPath.parseWithIds("/root/instance/{id}");
+ModelSpec<MyModel> modelSpec = ModelSpec.builder(path, serializer);
+ModeledFramework<MyModel> modeledClient = ModeledFramework.wrap(modelSpec, client, modelSpec);
+
+CachedModeledFramework<MyModel> cached = modeledClient.cached();
+cached.start(); // automatically listens to "/root/instance" and below
+{code}
+
+h2. Typed Parameters
+
+The "resolve" methods in ZPath et al consume untyped Objects. Ideally, we should be able to
+specify parameters in a strongly typed manner. Modeled Curator's "type" templates provide this. You
+can specify typed parameters for ZPaths, ModelSpecs and ModeledFramework.
+The [[Pub\-Sub Example|https://github.com/apache/curator/tree/master/curator-examples/src/main/java/pubsub]]
+shows how to use typed parameters with ModeledFramework.
+
+Typed interfaces are provided for up to 10 parameters and are named
+{{TypedZPath}}, {{TypedZPath2}}, {{TypedModelSpec}}, {{TypedModelSpec2}}, {{TypedModeledFramework}},
+{{TypedModeledFramework2}}, etc.
+
+Here's an example of a TypedModeledFramework that models a Person and uses two parameters
+to generate the path, a Group and an Organization:
+
+{code}
+TypedModeledFramework2<Person, Group, Organization> clientTemplate = TypedModeledFramework2.from(
+    ModeledFrameworkBuilder.build(),
+    personModelSpec
+);
+
+...
+
+Group group = ...
+Organization organization = ...
+ModeledFramework<Person> modeledClient = clientTemplate.resolve(asyncClient, group, organization);
+client.set(person);
+{code}
+
+TypedZPath and TypedModelSpec work similarly.
+
+h2. Versioning
+
+Modeled Curator supports associating a ZNode version with a model object via
+the {{Versioned}} interface and the {{VersionedModeledFramework}} APIs. To
+read a model along with its ZNode version use:
+
+{code}
+ModeledFramework<Person> client = ...
+
+client.versioned().read().whenComplete((value, e) -> {
+    if ( value != null ) {
+        // value's type is Versioned<Person>
+        Person personModel = value.model();
+        int znodeVersion = value.version();
+    }
+});
+{code}
+
+{{VersionedModeledFramework}} has set/update APIs which automatically use the version
+from the {{Versioned}} instance.

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/site/confluence/modeled.confluence
----------------------------------------------------------------------
diff --git a/curator-x-async/src/site/confluence/modeled.confluence b/curator-x-async/src/site/confluence/modeled.confluence
new file mode 100644
index 0000000..32e8727
--- /dev/null
+++ b/curator-x-async/src/site/confluence/modeled.confluence
@@ -0,0 +1,48 @@
+h1. Modeled Curator
+
+This is a strongly typed DSL that allows you to map a Curator\-style client to:
+
+* A ZooKeeper path (supporting parameterized substitutions)
+* A serializer for the data stored at the path
+* Options for how nodes should be created (sequential, compressed data, ttl, etc.)
+* ACLs for the nodes at the path
+* Options for how to delete nodes (guaranteed, deleting children, etc.)
+
+For example:
+
+{code}
+ModeledFramework<Foo> modeled = ModeledFramework.wrap(client, fooModelSpec);
+modeled.set(new Foo());
+{code}
+
+This ModeledFramework instance knows the path to use, how to serialize the "Foo" instance,
+which create options and ACLs to use, etc.
+
+h2. Background and Usage
+
+Note: To use Modeled Curator, you should be familiar with Java 8's lambdas, CompletedFuture and CompletionStage.
+You should also be familiar with [[Curator Async|async.html]] as Modeled Curator is based on it.
+
+Modeled Curator consists of the components:
+
+* [[ZPath|modeled-components.html]]
+* [[ModelSpec|modeled-components.html]]
+* [[ModeledFramework|modeled-components.html]]
+
+Additional functionality is provided by:
+
+* [[CachedModeledFramework|modeled-typed.html]]
+* [[Typed Parameter Templates|modeled-typed.html]]
+* [[Versioning|modeled-typed.html]]
+
+h2. Example
+
+A complete example usage of Modeled Curator along with CachedModeledFramework and Typed Parameter Templates
+can be found here: [[https://github.com/apache/curator/tree/master/curator-examples/src/main/java/pubsub]].
+
+h2. Details
+
+For more details see:
+
+* [[Components|modeled-components.html]]
+* [[Caching, Typed Parameters and Versioning|modeled-typed.html]]

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/site/site.xml
----------------------------------------------------------------------
diff --git a/curator-x-async/src/site/site.xml b/curator-x-async/src/site/site.xml
index 63fccaa..f78abc7 100644
--- a/curator-x-async/src/site/site.xml
+++ b/curator-x-async/src/site/site.xml
@@ -25,7 +25,11 @@
             <link rel="stylesheet" href="../css/site.css" />
             <script type="text/javascript">
                 $(function(){
-                $('a[title="Curator RPC Proxy"]').parent().addClass("active");
+                    if ( location &amp;&amp; location.pathname &amp;&amp; location.pathname.endsWith('/index.html') ) {
+                        $('a[title="Java 8/Async"]').parent().addClass("active");
+                    } else {
+                        $('a[title="Strongly Typed Models"]').parent().addClass("active");
+                    }
                 });
             </script>
         </head>

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java b/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java
new file mode 100644
index 0000000..232d301
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/CompletableBaseClassForTests.java
@@ -0,0 +1,65 @@
+/**
+ * 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.test.BaseClassForTests;
+import org.apache.curator.test.Timing;
+import org.testng.Assert;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+
+public abstract class CompletableBaseClassForTests extends BaseClassForTests
+{
+    protected static final Timing timing = new Timing();
+
+    protected <T, U> void complete(CompletionStage<T> stage)
+    {
+        complete(stage, (v, e) -> {});
+    }
+
+    protected <T, U> void complete(CompletionStage<T> stage, BiConsumer<? super T, Throwable> handler)
+    {
+        try
+        {
+            stage.handle((v, e) -> {
+                handler.accept(v, e);
+                return null;
+            }).toCompletableFuture().get(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS);
+        }
+        catch ( InterruptedException e )
+        {
+            Thread.interrupted();
+        }
+        catch ( ExecutionException e )
+        {
+            if ( e.getCause() instanceof AssertionError )
+            {
+                throw (AssertionError)e.getCause();
+            }
+            Assert.fail("get() failed", e);
+        }
+        catch ( TimeoutException e )
+        {
+            Assert.fail("get() timed out");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/TestAsyncWrappers.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/TestAsyncWrappers.java b/curator-x-async/src/test/java/org/apache/curator/x/async/TestAsyncWrappers.java
new file mode 100644
index 0000000..7ce7904
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/TestAsyncWrappers.java
@@ -0,0 +1,73 @@
+/**
+ * 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.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.framework.recipes.locks.InterProcessMutex;
+import org.apache.curator.retry.RetryOneTime;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestAsyncWrappers extends CompletableBaseClassForTests
+{
+    @Test
+    public void testBasic()
+    {
+        try ( CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new RetryOneTime(1)) )
+        {
+            client.start();
+
+            InterProcessMutex lock = new InterProcessMutex(client, "/one/two");
+            complete(AsyncWrappers.lockAsync(lock), (__, e) -> {
+                Assert.assertNull(e);
+                AsyncWrappers.release(lock);
+            });
+        }
+    }
+
+    @Test
+    public void testContention() throws Exception
+    {
+        try ( CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), new RetryOneTime(1)) )
+        {
+            client.start();
+
+            InterProcessMutex lock1 = new InterProcessMutex(client, "/one/two");
+            InterProcessMutex lock2 = new InterProcessMutex(client, "/one/two");
+            CountDownLatch latch = new CountDownLatch(1);
+            AsyncWrappers.lockAsync(lock1).thenAccept(__ -> {
+                latch.countDown();  // don't release the lock
+            });
+            Assert.assertTrue(timing.awaitLatch(latch));
+
+            CountDownLatch latch2 = new CountDownLatch(1);
+            AsyncWrappers.lockAsync(lock2, timing.forSleepingABit().milliseconds(), TimeUnit.MILLISECONDS).exceptionally(e -> {
+                if ( e instanceof AsyncWrappers.TimeoutException )
+                {
+                    latch2.countDown();  // lock should still be held
+                }
+                return null;
+            });
+            Assert.assertTrue(timing.awaitLatch(latch2));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/TestBasicOperations.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/TestBasicOperations.java b/curator-x-async/src/test/java/org/apache/curator/x/async/TestBasicOperations.java
index 1c4f241..0274413 100644
--- a/curator-x-async/src/test/java/org/apache/curator/x/async/TestBasicOperations.java
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/TestBasicOperations.java
@@ -44,9 +44,8 @@ import static org.apache.curator.x.async.api.CreateOption.compress;
 import static org.apache.curator.x.async.api.CreateOption.setDataIfExists;
 import static org.apache.zookeeper.CreateMode.EPHEMERAL_SEQUENTIAL;
 
-public class TestBasicOperations extends BaseClassForTests
+public class TestBasicOperations extends CompletableBaseClassForTests
 {
-    private static final Timing timing = new Timing();
     private AsyncCuratorFramework client;
 
     @BeforeMethod
@@ -181,36 +180,4 @@ public class TestBasicOperations extends BaseClassForTests
             Assert.assertEquals(v.getCode(), KeeperException.Code.CONNECTIONLOSS);
         });
     }
-
-    private <T, U> void complete(CompletionStage<T> stage)
-    {
-        complete(stage, (v, e) -> {});
-    }
-
-    private <T, U> void complete(CompletionStage<T> stage, BiConsumer<? super T, Throwable> handler)
-    {
-        try
-        {
-            stage.handle((v, e) -> {
-                handler.accept(v, e);
-                return null;
-            }).toCompletableFuture().get(timing.forWaiting().milliseconds(), TimeUnit.MILLISECONDS);
-        }
-        catch ( InterruptedException e )
-        {
-            Thread.interrupted();
-        }
-        catch ( ExecutionException e )
-        {
-            if ( e.getCause() instanceof AssertionError )
-            {
-                throw (AssertionError)e.getCause();
-            }
-            Assert.fail("get() failed", e);
-        }
-        catch ( TimeoutException e )
-        {
-            Assert.fail("get() timed out");
-        }
-    }
 }

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestCachedModeledFramework.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestCachedModeledFramework.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestCachedModeledFramework.java
new file mode 100644
index 0000000..49821e2
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestCachedModeledFramework.java
@@ -0,0 +1,167 @@
+/**
+ * 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.state.ConnectionState;
+import org.apache.curator.test.Timing;
+import org.apache.curator.x.async.modeled.cached.CachedModeledFramework;
+import org.apache.curator.x.async.modeled.cached.ModeledCacheListener;
+import org.apache.curator.x.async.modeled.models.TestModel;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.atomic.AtomicReference;
+
+public class TestCachedModeledFramework extends TestModeledFrameworkBase
+{
+    @Test
+    public void testThreading()
+    {
+        TestModel model = new TestModel("a", "b", "c", 1, BigInteger.ONE);
+        CachedModeledFramework<TestModel> client = ModeledFramework.wrap(async, modelSpec).cached().asyncDefault();
+
+        CountDownLatch latch = new CountDownLatch(1);
+        client.listenable().addListener((type, path1, stat, model1) -> latch.countDown());
+
+        complete(client.set(model));
+        client.start();
+        try
+        {
+            Assert.assertTrue(new Timing().awaitLatch(latch));
+
+            AtomicReference<Thread> completionThread = new AtomicReference<>();
+            complete(client.read().whenCompleteAsync((s, e) -> completionThread.set((e == null) ? Thread.currentThread() : null)));
+            Assert.assertNotNull(completionThread.get());
+            Assert.assertNotEquals(Thread.currentThread(), completionThread.get(), "Should be different threads");
+            completionThread.set(null);
+
+            complete(client.child("foo").read().whenCompleteAsync((v, e) -> completionThread.set((e != null) ? Thread.currentThread() : null)));
+            Assert.assertNotNull(completionThread.get());
+            Assert.assertNotEquals(Thread.currentThread(), completionThread.get(), "Should be different threads");
+            completionThread.set(null);
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+
+    @Test
+    public void testCustomThreading()
+    {
+        AtomicReference<Thread> ourThread = new AtomicReference<>();
+        ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
+            Thread thread = new Thread(r, "testCustomThreading");
+            ourThread.set(thread);
+            return thread;
+        });
+        TestModel model = new TestModel("a", "b", "c", 1, BigInteger.ONE);
+        CachedModeledFramework<TestModel> client = ModeledFramework.wrap(async, modelSpec).cached(executor).asyncDefault();
+
+        CountDownLatch latch = new CountDownLatch(1);
+        client.listenable().addListener((type, path1, stat, model1) -> latch.countDown());
+
+        complete(client.set(model));
+        client.start();
+        try
+        {
+            Assert.assertTrue(new Timing().awaitLatch(latch));
+
+            AtomicReference<Thread> completionThread = new AtomicReference<>();
+            complete(client.read().thenAcceptAsync(s -> completionThread.set(Thread.currentThread())));
+            Assert.assertEquals(ourThread.get(), completionThread.get(), "Should be our thread");
+            completionThread.set(null);
+
+            complete(client.child("foo").read().whenCompleteAsync((v, e) -> completionThread.set((e != null) ? Thread.currentThread() : null)));
+            Assert.assertEquals(ourThread.get(), completionThread.get(), "Should be our thread");
+            completionThread.set(null);
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+
+    @Test
+    public void testDownServer() throws IOException
+    {
+        Timing timing = new Timing();
+
+        TestModel model = new TestModel("a", "b", "c", 1, BigInteger.ONE);
+        CachedModeledFramework<TestModel> client = ModeledFramework.wrap(async, modelSpec).cached();
+        Semaphore semaphore = new Semaphore(0);
+        client.listenable().addListener((t, p, s, m) -> semaphore.release());
+
+        client.start();
+        try
+        {
+            client.set(model);
+            Assert.assertTrue(timing.acquireSemaphore(semaphore));
+
+            CountDownLatch latch = new CountDownLatch(1);
+            rawClient.getConnectionStateListenable().addListener((__, state) -> {
+                if ( state == ConnectionState.LOST )
+                {
+                    latch.countDown();
+                }
+            });
+            server.stop();
+            Assert.assertTrue(timing.awaitLatch(latch));
+
+            complete(client.read().whenComplete((value, e) -> {
+                Assert.assertNotNull(value);
+                Assert.assertNull(e);
+            }));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+
+    @Test
+    public void testPostInitializedFilter()
+    {
+        TestModel model1 = new TestModel("a", "b", "c", 1, BigInteger.ONE);
+        TestModel model2 = new TestModel("d", "e", "e", 1, BigInteger.ONE);
+        CachedModeledFramework<TestModel> client = ModeledFramework.wrap(async, modelSpec).cached();
+        Semaphore semaphore = new Semaphore(0);
+        ModeledCacheListener<TestModel> listener = (t, p, s, m) -> semaphore.release();
+        client.listenable().addListener(listener.postInitializedOnly());
+
+        complete(client.child("1").set(model1));  // set before cache is started
+        client.start();
+        try
+        {
+            Assert.assertFalse(timing.forSleepingABit().acquireSemaphore(semaphore));
+
+            client.child("2").set(model2);  // set before cache is started
+            Assert.assertTrue(timing.acquireSemaphore(semaphore));
+        }
+        finally
+        {
+            client.close();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFramework.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFramework.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFramework.java
new file mode 100644
index 0000000..42a9e63
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFramework.java
@@ -0,0 +1,178 @@
+/**
+ * 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.Sets;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.framework.schema.Schema;
+import org.apache.curator.framework.schema.SchemaSet;
+import org.apache.curator.framework.schema.SchemaViolation;
+import org.apache.curator.retry.RetryOneTime;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.AsyncStage;
+import org.apache.curator.x.async.modeled.models.TestModel;
+import org.apache.curator.x.async.modeled.models.TestNewerModel;
+import org.apache.curator.x.async.modeled.versioned.Versioned;
+import org.apache.curator.x.async.modeled.versioned.VersionedModeledFramework;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZooDefs;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Id;
+import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import java.math.BigInteger;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+public class TestModeledFramework extends TestModeledFrameworkBase
+{
+    @Test
+    public void testCrud()
+    {
+        TestModel rawModel = new TestModel("John", "Galt", "1 Galt's Gulch", 42, BigInteger.valueOf(1));
+        TestModel rawModel2 = new TestModel("Wayne", "Rooney", "Old Trafford", 10, BigInteger.valueOf(1));
+        ModeledFramework<TestModel> client = ModeledFramework.wrap(async, modelSpec);
+        AsyncStage<String> stage = client.set(rawModel);
+        Assert.assertNull(stage.event());
+        complete(stage, (s, e) -> Assert.assertNotNull(s));
+        complete(client.read(), (model, e) -> Assert.assertEquals(model, rawModel));
+        complete(client.update(rawModel2));
+        complete(client.read(), (model, e) -> Assert.assertEquals(model, rawModel2));
+        complete(client.delete());
+        complete(client.checkExists(), (stat, e) -> Assert.assertNull(stat));
+    }
+
+    @Test
+    public void testBackwardCompatibility()
+    {
+        TestNewerModel rawNewModel = new TestNewerModel("John", "Galt", "1 Galt's Gulch", 42, BigInteger.valueOf(1), 100);
+        ModeledFramework<TestNewerModel> clientForNew = ModeledFramework.wrap(async, newModelSpec);
+        complete(clientForNew.set(rawNewModel), (s, e) -> Assert.assertNotNull(s));
+
+        ModeledFramework<TestModel> clientForOld = ModeledFramework.wrap(async, modelSpec);
+        complete(clientForOld.read(), (model, e) -> Assert.assertTrue(rawNewModel.equalsOld(model)));
+    }
+
+    @Test
+    public void testWatched() throws InterruptedException
+    {
+        CountDownLatch latch = new CountDownLatch(1);
+        ModeledFramework<TestModel> client = ModeledFramework.builder(async, modelSpec).watched().build();
+        client.checkExists().event().whenComplete((event, ex) -> latch.countDown());
+        timing.sleepABit();
+        Assert.assertEquals(latch.getCount(), 1);
+        client.set(new TestModel());
+        Assert.assertTrue(timing.awaitLatch(latch));
+    }
+
+    @Test
+    public void testGetChildren()
+    {
+        TestModel model = new TestModel("John", "Galt", "1 Galt's Gulch", 42, BigInteger.valueOf(1));
+        ModeledFramework<TestModel> client = ModeledFramework.builder(async, modelSpec).build();
+        complete(client.child("one").set(model));
+        complete(client.child("two").set(model));
+        complete(client.child("three").set(model));
+
+        Set<ZPath> expected = Sets.newHashSet(path.child("one"), path.child("two"), path.child("three"));
+        complete(client.children(), (children, e) -> Assert.assertEquals(Sets.newHashSet(children), expected));
+    }
+
+    @Test
+    public void testBadNode()
+    {
+        complete(async.create().forPath(modelSpec.path().fullPath(), "fubar".getBytes()));
+
+        ModeledFramework<TestModel> client = ModeledFramework.builder(async, modelSpec).watched().build();
+        complete(client.read().whenComplete((model, e) -> Assert.assertTrue(e instanceof RuntimeException)));
+    }
+
+    @Test
+    public void testSchema() throws Exception
+    {
+        Schema schema = modelSpec.schema();
+        try ( CuratorFramework schemaClient = CuratorFrameworkFactory.builder()
+            .connectString(server.getConnectString())
+            .retryPolicy(new RetryOneTime(1))
+            .schemaSet(new SchemaSet(Collections.singletonList(schema), false))
+            .build() ) {
+            schemaClient.start();
+
+            try
+            {
+                schemaClient.create().forPath(modelSpec.path().fullPath(), "asflasfas".getBytes());
+                Assert.fail("Should've thrown SchemaViolation");
+            }
+            catch ( SchemaViolation dummy )
+            {
+                // expected
+            }
+
+            ModeledFramework<TestModel> modeledSchemaClient = ModeledFramework.wrap(AsyncCuratorFramework.wrap(schemaClient), modelSpec);
+            complete(modeledSchemaClient.set(new TestModel("one", "two", "three", 4, BigInteger.ONE)), (dummy, e) -> Assert.assertNull(e));
+        }
+    }
+
+    @Test
+    public void testVersioned()
+    {
+        ModeledFramework<TestModel> client = ModeledFramework.wrap(async, modelSpec);
+        client.set(new TestModel("John", "Galt", "Galt's Gulch", 21, BigInteger.valueOf(1010101)));
+
+        VersionedModeledFramework<TestModel> versioned = client.versioned();
+        complete(versioned.read().whenComplete((v, e) -> {
+            Assert.assertNull(e);
+            Assert.assertTrue(v.version() > 0);
+        }).thenCompose(versioned::set).whenComplete((s, e) -> Assert.assertNull(e))); // version is correct should succeed
+
+        complete(versioned.read().whenComplete((v, e) -> {
+            Assert.assertNull(e);
+            Assert.assertTrue(v.version() > 0);
+        }).thenCompose(value -> {
+            Versioned<TestModel> badVersion = Versioned.from(value.model(), Integer.MAX_VALUE);
+            return versioned.set(badVersion);
+        }).whenComplete((s, e) -> Assert.assertTrue(e instanceof KeeperException.BadVersionException)));
+    }
+
+    @Test
+    public void testAcl() throws NoSuchAlgorithmException
+    {
+        List<ACL> aclList = Collections.singletonList(new ACL(ZooDefs.Perms.WRITE, new Id("digest", DigestAuthenticationProvider.generateDigest("test:test"))));
+        ModelSpec<TestModel> aclModelSpec = ModelSpec.builder(modelSpec.path(), modelSpec.serializer()).withAclList(aclList).build();
+        ModeledFramework<TestModel> client = ModeledFramework.wrap(async, aclModelSpec);
+        complete(client.set(new TestModel("John", "Galt", "Galt's Gulch", 21, BigInteger.valueOf(1010101))));
+        complete(client.update(new TestModel("John", "Galt", "Galt's Gulch", 54, BigInteger.valueOf(88))), (__, e) -> Assert.assertNotNull(e, "Should've gotten an auth failure"));
+
+        try ( CuratorFramework authCurator = CuratorFrameworkFactory.builder()
+            .connectString(server.getConnectString())
+            .retryPolicy(new RetryOneTime(1))
+            .authorization("digest", "test:test".getBytes())
+            .build() )
+        {
+            authCurator.start();
+            ModeledFramework<TestModel> authClient = ModeledFramework.wrap(AsyncCuratorFramework.wrap(authCurator), aclModelSpec);
+            complete(authClient.update(new TestModel("John", "Galt", "Galt's Gulch", 42, BigInteger.valueOf(66))), (__, e) -> Assert.assertNull(e, "Should've succeeded"));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFrameworkBase.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFrameworkBase.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFrameworkBase.java
new file mode 100644
index 0000000..61a4570
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestModeledFrameworkBase.java
@@ -0,0 +1,64 @@
+/**
+ * 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.CuratorFramework;
+import org.apache.curator.framework.CuratorFrameworkFactory;
+import org.apache.curator.retry.RetryOneTime;
+import org.apache.curator.utils.CloseableUtils;
+import org.apache.curator.x.async.AsyncCuratorFramework;
+import org.apache.curator.x.async.CompletableBaseClassForTests;
+import org.apache.curator.x.async.modeled.models.TestModel;
+import org.apache.curator.x.async.modeled.models.TestNewerModel;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+
+public class TestModeledFrameworkBase extends CompletableBaseClassForTests
+{
+    protected static final ZPath path = ZPath.parse("/test/path");
+    protected CuratorFramework rawClient;
+    protected ModelSpec<TestModel> modelSpec;
+    protected ModelSpec<TestNewerModel> newModelSpec;
+    protected AsyncCuratorFramework async;
+
+    @BeforeMethod
+    @Override
+    public void setup() throws Exception
+    {
+        super.setup();
+
+        rawClient = CuratorFrameworkFactory.newClient(server.getConnectString(), timing.session(), timing.connection(), new RetryOneTime(1));
+        rawClient.start();
+        async = AsyncCuratorFramework.wrap(rawClient);
+
+        JacksonModelSerializer<TestModel> serializer = JacksonModelSerializer.build(TestModel.class);
+        JacksonModelSerializer<TestNewerModel> newSerializer = JacksonModelSerializer.build(TestNewerModel.class);
+
+        modelSpec = ModelSpec.builder(path, serializer).build();
+        newModelSpec = ModelSpec.builder(path, newSerializer).build();
+    }
+
+    @AfterMethod
+    @Override
+    public void teardown() throws Exception
+    {
+        CloseableUtils.closeQuietly(rawClient);
+        super.teardown();
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestZPath.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestZPath.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestZPath.java
new file mode 100644
index 0000000..d2c24da
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/TestZPath.java
@@ -0,0 +1,126 @@
+/**
+ * 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.utils.ZKPaths;
+import org.apache.curator.x.async.modeled.details.ZPathImpl;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import static org.apache.curator.x.async.modeled.ZPath.parameter;
+
+public class TestZPath
+{
+    @Test
+    public void testRoot()
+    {
+        Assert.assertEquals(ZPath.root.nodeName(), ZKPaths.PATH_SEPARATOR);
+        Assert.assertEquals(ZPath.root, ZPathImpl.root);
+        Assert.assertTrue(ZPath.root.isRoot());
+        Assert.assertEquals(ZPath.root.child("foo").parent(), ZPath.root);
+        Assert.assertTrue(ZPath.root.child("foo").parent().isRoot());
+    }
+
+    @Test
+    public void testBasic()
+    {
+        ZPath path = ZPath.root.child("one").child("two");
+        Assert.assertFalse(path.isRoot());
+        Assert.assertEquals(path, ZPath.root.child("one").child("two"));
+        Assert.assertNotEquals(path, ZPath.root.child("onex").child("two"));
+        Assert.assertEquals(path.nodeName(), "two");
+        Assert.assertEquals(path.fullPath(), "/one/two");
+        Assert.assertEquals(path.parent().fullPath(), "/one");
+        Assert.assertEquals(path.fullPath(), "/one/two");       // call twice to test the internal cache
+        Assert.assertEquals(path.parent().fullPath(), "/one");  // call twice to test the internal cache
+
+        Assert.assertTrue(path.startsWith(ZPath.root.child("one")));
+        Assert.assertFalse(path.startsWith(ZPath.root.child("two")));
+
+        ZPath checkIdLike = ZPath.parse("/one/{two}/three");
+        Assert.assertTrue(checkIdLike.isResolved());
+        checkIdLike = ZPath.parse("/one/" + ZPath.parameter() + "/three");
+        Assert.assertTrue(checkIdLike.isResolved());
+        checkIdLike = ZPath.parse("/one/" + ZPath.parameter("others") + "/three");
+        Assert.assertTrue(checkIdLike.isResolved());
+    }
+
+    @Test
+    public void testParsing()
+    {
+        Assert.assertEquals(ZPath.parse("/"), ZPath.root);
+        Assert.assertEquals(ZPath.parse("/one/two/three"), ZPath.root.child("one").child("two").child("three"));
+        Assert.assertEquals(ZPath.parse("/one/two/three"), ZPath.from("one", "two", "three"));
+        Assert.assertEquals(ZPath.parseWithIds("/one/{id}/two/{id}"), ZPath.from("one", parameter(), "two", parameter()));
+    }
+
+    @Test(expectedExceptions = IllegalStateException.class)
+    public void testUnresolvedPath()
+    {
+        ZPath path = ZPath.from("one", parameter(), "two");
+        path.fullPath();
+    }
+
+    @Test
+    public void testResolvedPath()
+    {
+        ZPath path = ZPath.from("one", parameter(), "two", parameter());
+        Assert.assertEquals(path.resolved("a", "b"), ZPath.from("one", "a", "two", "b"));
+    }
+
+    @Test
+    public void testSchema()
+    {
+        ZPath path = ZPath.from("one", parameter(), "two", parameter());
+        Assert.assertEquals(path.toSchemaPathPattern().toString(), "/one/.*/two/.*");
+        path = ZPath.parse("/one/two/three");
+        Assert.assertEquals(path.toSchemaPathPattern().toString(), "/one/two/three");
+        path = ZPath.parseWithIds("/one/{id}/three");
+        Assert.assertEquals(path.toSchemaPathPattern().toString(), "/one/.*/three");
+        path = ZPath.parseWithIds("/{id}/{id}/three");
+        Assert.assertEquals(path.toSchemaPathPattern().toString(), "/.*/.*/three");
+    }
+
+    @Test
+    public void testCustomIds()
+    {
+        Assert.assertEquals(ZPath.parseWithIds("/a/{a}/bee/{bee}/c/{c}").toString(), "/a/{a}/bee/{bee}/c/{c}");
+        Assert.assertEquals(ZPath.from("a", parameter(), "b", parameter()).toString(), "/a/{id}/b/{id}");
+        Assert.assertEquals(ZPath.from("a", parameter("foo"), "b", parameter("bar")).toString(), "/a/{foo}/b/{bar}");
+    }
+
+    @Test
+    public void testPartialResolution()
+    {
+        ZPath path = ZPath.parseWithIds("/one/{1}/two/{2}");
+        Assert.assertFalse(path.parent().isResolved());
+        Assert.assertFalse(path.parent().parent().isResolved());
+        Assert.assertTrue(path.parent().parent().parent().isResolved());
+        Assert.assertFalse(path.isResolved());
+
+        path = path.resolved("p1");
+        Assert.assertFalse(path.isResolved());
+        Assert.assertTrue(path.parent().isResolved());
+        Assert.assertEquals(path.toString(), "/one/p1/two/{2}");
+
+        path = path.resolved("p2");
+        Assert.assertTrue(path.isResolved());
+        Assert.assertEquals(path.toString(), "/one/p1/two/p2");
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestModel.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestModel.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestModel.java
new file mode 100644
index 0000000..8a92d33
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestModel.java
@@ -0,0 +1,115 @@
+/**
+ * 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.models;
+
+import java.math.BigInteger;
+import java.util.Objects;
+
+public class TestModel
+{
+    private final String firstName;
+    private final String lastName;
+    private final String address;
+    private final int age;
+    private final BigInteger salary;
+
+    public TestModel()
+    {
+        this("", "", "", 0, BigInteger.ZERO);
+    }
+
+    public TestModel(String firstName, String lastName, String address, int age, BigInteger salary)
+    {
+        this.firstName = Objects.requireNonNull(firstName, "firstName cannot be null");
+        this.lastName = Objects.requireNonNull(lastName, "lastName cannot be null");
+        this.address = Objects.requireNonNull(address, "address cannot be null");
+        this.age = Objects.requireNonNull(age, "age cannot be null");
+        this.salary = salary;
+    }
+
+    public String getFirstName()
+    {
+        return firstName;
+    }
+
+    public String getLastName()
+    {
+        return lastName;
+    }
+
+    public String getAddress()
+    {
+        return address;
+    }
+
+    public int getAge()
+    {
+        return age;
+    }
+
+    public BigInteger getSalary()
+    {
+        return salary;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        TestModel testModel = (TestModel)o;
+
+        if ( age != testModel.age )
+        {
+            return false;
+        }
+        if ( !firstName.equals(testModel.firstName) )
+        {
+            return false;
+        }
+        if ( !lastName.equals(testModel.lastName) )
+        {
+            return false;
+        }
+        //noinspection SimplifiableIfStatement
+        if ( !address.equals(testModel.address) )
+        {
+            return false;
+        }
+        return salary.equals(testModel.salary);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = firstName.hashCode();
+        result = 31 * result + lastName.hashCode();
+        result = 31 * result + address.hashCode();
+        result = 31 * result + age;
+        result = 31 * result + salary.hashCode();
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestNewerModel.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestNewerModel.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestNewerModel.java
new file mode 100644
index 0000000..94e82fb
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestNewerModel.java
@@ -0,0 +1,137 @@
+/**
+ * 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.models;
+
+import java.math.BigInteger;
+import java.util.Objects;
+
+public class TestNewerModel
+{
+    private final String firstName;
+    private final String lastName;
+    private final String address;
+    private final int age;
+    private final BigInteger salary;
+    private final long newField;
+
+    public TestNewerModel()
+    {
+        this("", "", "", 0, BigInteger.ZERO, 0);
+    }
+
+    public TestNewerModel(String firstName, String lastName, String address, int age, BigInteger salary, long newField)
+    {
+        this.firstName = Objects.requireNonNull(firstName, "firstName cannot be null");
+        this.lastName = Objects.requireNonNull(lastName, "lastName cannot be null");
+        this.address = Objects.requireNonNull(address, "address cannot be null");
+        this.age = Objects.requireNonNull(age, "age cannot be null");
+        this.salary = salary;
+        this.newField = newField;
+    }
+
+    public String getFirstName()
+    {
+        return firstName;
+    }
+
+    public String getLastName()
+    {
+        return lastName;
+    }
+
+    public String getAddress()
+    {
+        return address;
+    }
+
+    public int getAge()
+    {
+        return age;
+    }
+
+    public BigInteger getSalary()
+    {
+        return salary;
+    }
+
+    public long getNewField()
+    {
+        return newField;
+    }
+
+    public boolean equalsOld(TestModel model)
+    {
+        return firstName.equals(model.getFirstName())
+            && lastName.equals(model.getLastName())
+            && address.equals(model.getAddress())
+            && salary.equals(model.getSalary())
+            && age == model.getAge()
+            ;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        TestNewerModel that = (TestNewerModel)o;
+
+        if ( age != that.age )
+        {
+            return false;
+        }
+        if ( newField != that.newField )
+        {
+            return false;
+        }
+        if ( !firstName.equals(that.firstName) )
+        {
+            return false;
+        }
+        if ( !lastName.equals(that.lastName) )
+        {
+            return false;
+        }
+        //noinspection SimplifiableIfStatement
+        if ( !address.equals(that.address) )
+        {
+            return false;
+        }
+        return salary.equals(that.salary);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = firstName.hashCode();
+        result = 31 * result + lastName.hashCode();
+        result = 31 * result + address.hashCode();
+        result = 31 * result + age;
+        result = 31 * result + salary.hashCode();
+        result = 31 * result + (int)(newField ^ (newField >>> 32));
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestSimpleModel.java
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestSimpleModel.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestSimpleModel.java
new file mode 100644
index 0000000..f998da2
--- /dev/null
+++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/models/TestSimpleModel.java
@@ -0,0 +1,84 @@
+/**
+ * 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.models;
+
+import java.util.Objects;
+
+public class TestSimpleModel
+{
+    private final String name;
+    private final int age;
+
+    public TestSimpleModel()
+    {
+        this("", 0);
+    }
+
+    public TestSimpleModel(String name, int age)
+    {
+        this.name = Objects.requireNonNull(name, "name cannot be null");
+        this.age = Objects.requireNonNull(age, "age cannot be null");
+    }
+
+    public String getName()
+    {
+        return name;
+    }
+
+    public int getAge()
+    {
+        return age;
+    }
+
+    @Override
+    public boolean equals(Object o)
+    {
+        if ( this == o )
+        {
+            return true;
+        }
+        if ( o == null || getClass() != o.getClass() )
+        {
+            return false;
+        }
+
+        TestSimpleModel that = (TestSimpleModel)o;
+
+        //noinspection SimplifiableIfStatement
+        if ( age != that.age )
+        {
+            return false;
+        }
+        return name.equals(that.name);
+    }
+
+    @Override
+    public int hashCode()
+    {
+        int result = name.hashCode();
+        result = 31 * result + age;
+        return result;
+    }
+
+    @Override
+    public String toString()
+    {
+        return "TestSimpleModel{" + "name='" + name + '\'' + ", age=" + age + '}';
+    }
+}

http://git-wip-us.apache.org/repos/asf/curator/blob/0f5d10da/curator-x-async/src/test/resources/log4j.properties
----------------------------------------------------------------------
diff --git a/curator-x-async/src/test/resources/log4j.properties b/curator-x-async/src/test/resources/log4j.properties
new file mode 100644
index 0000000..2a85e0d
--- /dev/null
+++ b/curator-x-async/src/test/resources/log4j.properties
@@ -0,0 +1,27 @@
+#
+# 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.logger.org.apache.curator=DEBUG, console
+log4j.additivity.org.apache.curator=false
+
+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