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:34 UTC
[2/6] curator git commit: Squashed commit of the following:
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 && location.pathname && 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