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
