Repository: curator Updated Branches: refs/heads/CURATOR-421 [created] 1784a7c68
initial work on migrations done. Needs lots of testing and doc. However, found bug CURATOR-423 and will need to fix that first Project: http://git-wip-us.apache.org/repos/asf/curator/repo Commit: http://git-wip-us.apache.org/repos/asf/curator/commit/4cd731c9 Tree: http://git-wip-us.apache.org/repos/asf/curator/tree/4cd731c9 Diff: http://git-wip-us.apache.org/repos/asf/curator/diff/4cd731c9 Branch: refs/heads/CURATOR-421 Commit: 4cd731c92d34a075d9d5a9610ec2610d69aa7792 Parents: 716fb4a Author: randgalt <[email protected]> Authored: Thu Jul 13 23:44:59 2017 -0500 Committer: randgalt <[email protected]> Committed: Thu Jul 13 23:44:59 2017 -0500 ---------------------------------------------------------------------- .../apache/curator/x/async/AsyncWrappers.java | 38 ++-- .../x/async/modeled/ModelSerializer.java | 18 ++ .../async/modeled/ModeledFrameworkBuilder.java | 20 +- .../curator/x/async/modeled/ModeledOptions.java | 29 +++ .../modeled/details/ModeledFrameworkImpl.java | 33 +++- .../InvalidMigrationSetException.java | 37 ++++ .../x/async/modeled/migrations/MetaData.java | 84 +++++++++ .../x/async/modeled/migrations/Migration.java | 57 ++++++ .../modeled/migrations/MigrationManager.java | 37 ++++ .../migrations/MigrationManagerBuilder.java | 68 +++++++ .../migrations/MigrationManagerImpl.java | 181 +++++++++++++++++++ .../async/modeled/migrations/MigrationSet.java | 69 +++++++ .../x/async/modeled/migrations/Result.java | 23 +++ .../x/async/CompletableBaseClassForTests.java | 8 +- .../migrations/TestMigrationManager.java | 129 +++++++++++++ .../modeled/migrations/models/ModelV1.java | 39 ++++ .../modeled/migrations/models/ModelV2.java | 46 +++++ .../modeled/migrations/models/ModelV3.java | 53 ++++++ 18 files changed, 934 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java index e982cf2..7da82fc 100644 --- a/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/AsyncWrappers.java @@ -20,7 +20,10 @@ package org.apache.curator.x.async; import org.apache.curator.framework.recipes.locks.InterProcessLock; import org.apache.curator.utils.ThreadUtils; +import org.apache.curator.x.async.api.ExistsOption; import org.apache.curator.x.async.modeled.ZPath; +import java.util.Collections; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.Executor; @@ -67,38 +70,21 @@ public class AsyncWrappers { /** * Asynchronously call {@link org.apache.curator.framework.CuratorFramework#createContainers(String)} using - * the {@link java.util.concurrent.ForkJoinPool#commonPool()}. - * - * @param client client - * @param path path to ensure - * @return stage - */ - public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path) - { - return asyncEnsureContainers(client, path, null); - } - - /** - * Asynchronously call {@link org.apache.curator.framework.CuratorFramework#createContainers(String)} using * the given executor * * @param client client * @param path path to ensure * @return stage */ - public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path, Executor executor) + public static CompletionStage<Void> asyncEnsureContainers(AsyncCuratorFramework client, ZPath path) { - Runnable proc = () -> { - try - { - client.unwrap().createContainers(path.fullPath()); - } - catch ( Exception e ) - { - throw new RuntimeException(e); - } - }; - return (executor != null) ? CompletableFuture.runAsync(proc, executor) : CompletableFuture.runAsync(proc); + Set<ExistsOption> options = Collections.singleton(ExistsOption.createParentsAsContainers); + return client + .checkExists() + .withOptions(options) + .forPath(path.child("foo").fullPath()) + .thenApply(__ -> null) + ; } /** @@ -284,7 +270,7 @@ public class AsyncWrappers future.complete(null); } } - catch ( Exception e ) + catch ( Throwable e ) { ThreadUtils.checkInterrupted(e); future.completeExceptionally(e); http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java index 428096e..476f314 100644 --- a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModelSerializer.java @@ -40,4 +40,22 @@ public interface ModelSerializer<T> * @throws RuntimeException if <code>bytes</code> is invalid or there was an error deserializing */ T deserialize(byte[] bytes); + + /** + * A pass through serializer + */ + ModelSerializer<byte[]> raw = new ModelSerializer<byte[]>() + { + @Override + public byte[] serialize(byte[] model) + { + return model; + } + + @Override + public byte[] deserialize(byte[] bytes) + { + return bytes; + } + }; } http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java index 2e8bec3..1df68e6 100644 --- a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledFrameworkBuilder.java @@ -18,13 +18,16 @@ */ package org.apache.curator.x.async.modeled; +import com.google.common.collect.ImmutableSet; import org.apache.curator.framework.api.CuratorEvent; import org.apache.curator.framework.api.UnhandledErrorListener; import org.apache.curator.x.async.AsyncCuratorFramework; import org.apache.curator.x.async.WatchMode; import org.apache.curator.x.async.modeled.details.ModeledFrameworkImpl; import org.apache.zookeeper.WatchedEvent; +import java.util.Collections; import java.util.Objects; +import java.util.Set; import java.util.function.UnaryOperator; public class ModeledFrameworkBuilder<T> @@ -35,6 +38,7 @@ public class ModeledFrameworkBuilder<T> private UnaryOperator<WatchedEvent> watcherFilter; private UnhandledErrorListener unhandledErrorListener; private UnaryOperator<CuratorEvent> resultFilter; + private Set<ModeledOptions> modeledOptions; /** * Build a new ModeledFramework instance @@ -49,7 +53,8 @@ public class ModeledFrameworkBuilder<T> watchMode, watcherFilter, unhandledErrorListener, - resultFilter + resultFilter, + modeledOptions ); } @@ -142,6 +147,18 @@ public class ModeledFrameworkBuilder<T> return this; } + /** + * Change the modeled options + * + * @param modeledOptions new options set + * @return this for chaining + */ + public ModeledFrameworkBuilder<T> withOptions(Set<ModeledOptions> modeledOptions) + { + this.modeledOptions = ImmutableSet.copyOf(Objects.requireNonNull(modeledOptions, "client cannot be null")); + return this; + } + ModeledFrameworkBuilder() { } @@ -150,5 +167,6 @@ public class ModeledFrameworkBuilder<T> { this.client = Objects.requireNonNull(client, "client cannot be null"); this.modelSpec = Objects.requireNonNull(modelSpec, "modelSpec cannot be null"); + modeledOptions = Collections.singleton(ModeledOptions.ignoreMissingNodesForChildren); } } http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java new file mode 100644 index 0000000..434894b --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/ModeledOptions.java @@ -0,0 +1,29 @@ +/** + * 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; + +public enum ModeledOptions +{ + /** + * Causes {@link ModeledFramework#children()} and {@link ModeledFramework#childrenAsZNodes()} + * to ignore {@link org.apache.zookeeper.KeeperException.NoNodeException} and merely return + * an empty list + */ + ignoreMissingNodesForChildren +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java index c1d19c4..44011ee 100644 --- a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/details/ModeledFrameworkImpl.java @@ -19,6 +19,8 @@ package org.apache.curator.x.async.modeled.details; import com.google.common.base.Preconditions; +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.apache.curator.framework.api.CuratorEvent; import org.apache.curator.framework.api.UnhandledErrorListener; @@ -36,13 +38,16 @@ import org.apache.curator.x.async.api.CreateOption; import org.apache.curator.x.async.api.WatchableAsyncCuratorFramework; import org.apache.curator.x.async.modeled.ModelSpec; import org.apache.curator.x.async.modeled.ModeledFramework; +import org.apache.curator.x.async.modeled.ModeledOptions; import org.apache.curator.x.async.modeled.ZNode; import org.apache.curator.x.async.modeled.ZPath; import org.apache.curator.x.async.modeled.cached.CachedModeledFramework; import org.apache.curator.x.async.modeled.versioned.VersionedModeledFramework; +import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.data.ACL; import org.apache.zookeeper.data.Stat; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; @@ -62,13 +67,15 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T> private final UnaryOperator<CuratorEvent> resultFilter; private final AsyncCuratorFrameworkDsl dslClient; private final boolean isWatched; + private final Set<ModeledOptions> modeledOptions; - public static <T> ModeledFrameworkImpl<T> build(AsyncCuratorFramework client, ModelSpec<T> model, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter) + public static <T> ModeledFrameworkImpl<T> build(AsyncCuratorFramework client, ModelSpec<T> model, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter, Set<ModeledOptions> modeledOptions) { boolean isWatched = (watchMode != null); Objects.requireNonNull(client, "client cannot be null"); Objects.requireNonNull(model, "model cannot be null"); + modeledOptions = ImmutableSet.copyOf(Objects.requireNonNull(modeledOptions, "modeledOptions cannot be null")); watchMode = (watchMode != null) ? watchMode : WatchMode.stateChangeAndSuccess; @@ -84,11 +91,12 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T> watcherFilter, unhandledErrorListener, resultFilter, - isWatched + isWatched, + modeledOptions ); } - private ModeledFrameworkImpl(AsyncCuratorFramework client, AsyncCuratorFrameworkDsl dslClient, WatchableAsyncCuratorFramework watchableClient, ModelSpec<T> modelSpec, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter, boolean isWatched) + private ModeledFrameworkImpl(AsyncCuratorFramework client, AsyncCuratorFrameworkDsl dslClient, WatchableAsyncCuratorFramework watchableClient, ModelSpec<T> modelSpec, WatchMode watchMode, UnaryOperator<WatchedEvent> watcherFilter, UnhandledErrorListener unhandledErrorListener, UnaryOperator<CuratorEvent> resultFilter, boolean isWatched, Set<ModeledOptions> modeledOptions) { this.client = client; this.dslClient = dslClient; @@ -99,6 +107,7 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T> this.unhandledErrorListener = unhandledErrorListener; this.resultFilter = resultFilter; this.isWatched = isWatched; + this.modeledOptions = modeledOptions; } @Override @@ -280,7 +289,14 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T> asyncStage.whenComplete((children, e) -> { if ( e != null ) { - modelStage.completeExceptionally(e); + if ( modeledOptions.contains(ModeledOptions.ignoreMissingNodesForChildren) && (Throwables.getRootCause(e) instanceof KeeperException.NoNodeException) ) + { + modelStage.complete(Collections.emptyList()); + } + else + { + modelStage.completeExceptionally(e); + } } else { @@ -303,7 +319,8 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T> watcherFilter, unhandledErrorListener, resultFilter, - isWatched + isWatched, + modeledOptions ); } @@ -320,7 +337,8 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T> watcherFilter, unhandledErrorListener, resultFilter, - isWatched + isWatched, + modeledOptions ); } @@ -337,7 +355,8 @@ public class ModeledFrameworkImpl<T> implements ModeledFramework<T> watcherFilter, unhandledErrorListener, resultFilter, - isWatched + isWatched, + modeledOptions ); } http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java new file mode 100644 index 0000000..84b21bf --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/InvalidMigrationSetException.java @@ -0,0 +1,37 @@ +/** + * 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.migrations; + +import java.util.Objects; + +public class InvalidMigrationSetException extends RuntimeException +{ + private final String migrationId; + + public InvalidMigrationSetException(String migrationId, String message) + { + super(message); + this.migrationId = Objects.requireNonNull(migrationId, "migrationId cannot be null"); + } + + public String getMigrationId() + { + return migrationId; + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.java new file mode 100644 index 0000000..c2878e2 --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MetaData.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.migrations; + +import java.util.Objects; + +public class MetaData +{ + private final String migrationId; + private final int migrationVersion; + + public MetaData() + { + this("", 0); + } + + public MetaData(String migrationId, int migrationVersion) + { + this.migrationId = Objects.requireNonNull(migrationId, "migrationId cannot be null"); + this.migrationVersion = migrationVersion; + } + + public String getMigrationId() + { + return migrationId; + } + + public int getMigrationVersion() + { + return migrationVersion; + } + + @Override + public boolean equals(Object o) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + + MetaData metaData = (MetaData)o; + + //noinspection SimplifiableIfStatement + if ( migrationVersion != metaData.migrationVersion ) + { + return false; + } + return migrationId.equals(metaData.migrationId); + } + + @Override + public int hashCode() + { + int result = migrationId.hashCode(); + result = 31 * result + migrationVersion; + return result; + } + + @Override + public String toString() + { + return "MetaData{" + "migrationId='" + migrationId + '\'' + ", migrationVersion=" + migrationVersion + '}'; + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java new file mode 100644 index 0000000..b3919d1 --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Migration.java @@ -0,0 +1,57 @@ +/** + * 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.migrations; + +import java.util.Objects; +import java.util.function.UnaryOperator; + +public interface Migration +{ + String id(); + + int version(); + + byte[] migrate(byte[] previousBytes); + + static Migration build(String id, int version, UnaryOperator<byte[]> migrateProc) + { + Objects.requireNonNull(id, "id cannot be null"); + Objects.requireNonNull(migrateProc, "migrateProc cannot be null"); + return new Migration() + { + @Override + public String id() + { + return id; + } + + @Override + public int version() + { + return version; + } + + @Override + public byte[] migrate(byte[] previousBytes) + { + return migrateProc.apply(previousBytes); + } + }; + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java new file mode 100644 index 0000000..2d5f39f --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManager.java @@ -0,0 +1,37 @@ +/** + * 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.migrations; + +import org.apache.curator.x.async.AsyncCuratorFramework; +import org.apache.curator.x.async.modeled.ModelSerializer; +import org.apache.curator.x.async.modeled.ZPath; +import java.util.List; +import java.util.concurrent.CompletionStage; + +public interface MigrationManager +{ + CompletionStage<List<MetaData>> metaData(ZPath metaDataPath); + + CompletionStage<Void> run(); + + static MigrationManagerBuilder builder(AsyncCuratorFramework client, ZPath lockPath, ModelSerializer<MetaData> metaDataSerializer) + { + return new MigrationManagerBuilder(client, lockPath, metaDataSerializer); + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java new file mode 100644 index 0000000..ed48242 --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerBuilder.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.curator.x.async.modeled.migrations; + +import org.apache.curator.x.async.AsyncCuratorFramework; +import org.apache.curator.x.async.modeled.ModelSerializer; +import org.apache.curator.x.async.modeled.ZPath; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.Executor; + +public class MigrationManagerBuilder +{ + private final AsyncCuratorFramework client; + private final ZPath lockPath; + private final ModelSerializer<MetaData> metaDataSerializer; + private final List<MigrationSet> sets = new ArrayList<>(); + private Executor executor = Runnable::run; + private Duration lockMax = Duration.ofSeconds(15); + + MigrationManagerBuilder(AsyncCuratorFramework client, ZPath lockPath, ModelSerializer<MetaData> metaDataSerializer) + { + this.client = Objects.requireNonNull(client, "client cannot be null"); + this.lockPath = Objects.requireNonNull(lockPath, "lockPath cannot be null"); + this.metaDataSerializer = Objects.requireNonNull(metaDataSerializer, "metaDataSerializer cannot be null"); + } + + public MigrationManager build() + { + return new MigrationManagerImpl(client, lockPath, metaDataSerializer, executor, lockMax, sets); + } + + public MigrationManagerBuilder withExecutor(Executor executor) + { + this.executor = Objects.requireNonNull(executor, "executor cannot be null"); + return this; + } + + public MigrationManagerBuilder withLockMax(Duration lockMax) + { + this.lockMax = Objects.requireNonNull(lockMax, "lockMax cannot be null"); + return this; + } + + public MigrationManagerBuilder adding(MigrationSet set) + { + sets.add(Objects.requireNonNull(set, "set cannot be null")); + return this; + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java new file mode 100644 index 0000000..15c61b2 --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationManagerImpl.java @@ -0,0 +1,181 @@ +/** + * 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.migrations; + +import com.google.common.base.Throwables; +import com.google.common.collect.ImmutableList; +import org.apache.curator.framework.api.transaction.CuratorOp; +import org.apache.curator.framework.recipes.locks.InterProcessLock; +import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex; +import org.apache.curator.x.async.AsyncCuratorFramework; +import org.apache.curator.x.async.modeled.ModelSerializer; +import org.apache.curator.x.async.modeled.ModelSpec; +import org.apache.curator.x.async.modeled.ModeledFramework; +import org.apache.curator.x.async.modeled.ZNode; +import org.apache.curator.x.async.modeled.ZPath; +import org.apache.zookeeper.CreateMode; +import java.time.Duration; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.apache.curator.x.async.AsyncWrappers.*; + +class MigrationManagerImpl implements MigrationManager +{ + private final AsyncCuratorFramework client; + private final ZPath lockPath; + private final ModelSerializer<MetaData> metaDataSerializer; + private final Executor executor; + private final Duration lockMax; + private final List<MigrationSet> sets; + + private static final String META_DATA_NODE_NAME = "meta-"; + + MigrationManagerImpl(AsyncCuratorFramework client, ZPath lockPath, ModelSerializer<MetaData> metaDataSerializer, Executor executor, Duration lockMax, List<MigrationSet> sets) + { + this.client = client; + this.lockPath = lockPath; + this.metaDataSerializer = metaDataSerializer; + this.executor = executor; + this.lockMax = lockMax; + this.sets = ImmutableList.copyOf(sets); + } + + @Override + public CompletionStage<List<MetaData>> metaData(ZPath metaDataPath) + { + ModeledFramework<MetaData> modeled = getMetaDataClient(metaDataPath); + return ZNode.models(modeled.childrenAsZNodes()); + } + + @Override + public CompletionStage<Void> run() + { + Map<String, CompletableFuture<Void>> futures = sets + .stream() + .map(m -> new AbstractMap.SimpleEntry<>(m.id(), runMigration(m).toCompletableFuture())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + return CompletableFuture.allOf(futures.values().toArray(new CompletableFuture[futures.size()])); + } + + private CompletionStage<Void> runMigration(MigrationSet set) + { + String lockPath = this.lockPath.child(set.id()).fullPath(); + InterProcessLock lock = new InterProcessSemaphoreMutex(client.unwrap(), lockPath); + CompletionStage<Void> lockStage = lockAsync(lock, lockMax.toMillis(), TimeUnit.MILLISECONDS, executor); + return lockStage.thenCompose(__ -> runMigrationInLock(lock, set)); + } + + private CompletionStage<Void> runMigrationInLock(InterProcessLock lock, MigrationSet set) + { + ModeledFramework<MetaData> modeled = getMetaDataClient(set.metaDataPath()); + return modeled.childrenAsZNodes() + .thenCompose(metaData -> applyMetaData(set, modeled, metaData)) + .handle((v, e) -> { + release(lock, true); + if ( e != null ) + { + Throwables.propagate(e); + } + return v; + } + ); + } + + private ModeledFramework<MetaData> getMetaDataClient(ZPath metaDataPath) + { + ModelSpec<MetaData> modelSpec = ModelSpec.builder(metaDataPath, metaDataSerializer).withCreateMode(CreateMode.PERSISTENT_SEQUENTIAL).build(); + return ModeledFramework.wrap(client, modelSpec); + } + + protected void checkIsValid(MigrationSet set, List<MetaData> sortedMetaData) throws InvalidMigrationSetException + { + if ( sortedMetaData.size() > set.migrations().size() ) + { + throw new InvalidMigrationSetException(set.id(), String.format("More metadata than migrations. Migration ID: %s - MetaData: %s", set.id(), sortedMetaData)); + } + + int compareSize = Math.min(set.migrations().size(), sortedMetaData.size()); + List<MetaData> compareMigrations = set.migrations().subList(0, compareSize) + .stream() + .map(m -> new MetaData(m.id(), m.version())) + .collect(Collectors.toList()); + if ( !compareMigrations.equals(sortedMetaData) ) + { + throw new InvalidMigrationSetException(set.id(), String.format("Metadata mismatch. Migration ID: %s - MetaData: %s", set.id(), sortedMetaData)); + } + } + + private CompletionStage<Void> applyMetaData(MigrationSet set, ModeledFramework<MetaData> metaDataClient, List<ZNode<MetaData>> metaDataNodes) + { + List<MetaData> sortedMetaData = metaDataNodes + .stream() + .sorted(Comparator.comparing(m -> m.path().fullPath())) + .map(ZNode::model) + .collect(Collectors.toList()); + try + { + checkIsValid(set, sortedMetaData); + } + catch ( InvalidMigrationSetException e ) + { + CompletableFuture<Void> future = new CompletableFuture<>(); + future.completeExceptionally(e); + return future; + } + + List<Migration> toBeApplied = set.migrations().subList(sortedMetaData.size(), set.migrations().size()); + if ( toBeApplied.size() == 0 ) + { + return CompletableFuture.completedFuture(null); + } + + return asyncEnsureContainers(client, metaDataClient.modelSpec().path()) + .thenCompose(__ -> applyMetaDataAfterEnsure(set, toBeApplied, metaDataClient)); + } + + private CompletionStage<Void> applyMetaDataAfterEnsure(MigrationSet set, List<Migration> toBeApplied, ModeledFramework<MetaData> metaDataClient) + { + ModelSpec<byte[]> modelSpec = ModelSpec.builder(set.path(), ModelSerializer.raw).build(); + ModeledFramework<byte[]> modeled = ModeledFramework.wrap(client, modelSpec); + return modeled.childrenAsZNodes().thenCompose(nodes -> { + List<CuratorOp> operations = new ArrayList<>(); + for ( ZNode<byte[]> node : nodes ) + { + byte[] currentBytes = node.model(); + for ( Migration migration : toBeApplied ) + { + currentBytes = migration.migrate(currentBytes); + MetaData thisMetaData = new MetaData(migration.id(), migration.version()); + operations.add(metaDataClient.child(META_DATA_NODE_NAME).createOp(thisMetaData)); + } + operations.add(modeled.child(node.path().nodeName()).updateOp(currentBytes, node.stat().getVersion())); + } + return client.transaction().forOperations(operations).thenApply(__ -> null); + }); + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.java new file mode 100644 index 0000000..9e41989 --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/MigrationSet.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.migrations; + +import com.google.common.collect.ImmutableList; +import org.apache.curator.x.async.modeled.ZPath; +import java.util.List; +import java.util.Objects; + +public interface MigrationSet +{ + String id(); + + ZPath path(); + + ZPath metaDataPath(); + + List<Migration> migrations(); + + static MigrationSet build(String id, ZPath path, ZPath metaDataPath, List<Migration> migrations) + { + Objects.requireNonNull(id, "id cannot be null"); + Objects.requireNonNull(path, "path cannot be null"); + Objects.requireNonNull(metaDataPath, "metaDataPath cannot be null"); + final List<Migration> migrationsCopy = ImmutableList.copyOf(migrations); + return new MigrationSet() + { + @Override + public String id() + { + return id; + } + + @Override + public ZPath path() + { + return path; + } + + @Override + public ZPath metaDataPath() + { + return metaDataPath; + } + + @Override + public List<Migration> migrations() + { + return migrationsCopy; + } + }; + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java new file mode 100644 index 0000000..4fcfeac --- /dev/null +++ b/curator-x-async/src/main/java/org/apache/curator/x/async/modeled/migrations/Result.java @@ -0,0 +1,23 @@ +/** + * 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.migrations; + +public interface Result +{ +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/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 index 232d301..4a964b1 100644 --- 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 @@ -18,6 +18,7 @@ */ package org.apache.curator.x.async; +import com.google.common.base.Throwables; import org.apache.curator.test.BaseClassForTests; import org.apache.curator.test.Timing; import org.testng.Assert; @@ -33,7 +34,12 @@ public abstract class CompletableBaseClassForTests extends BaseClassForTests protected <T, U> void complete(CompletionStage<T> stage) { - complete(stage, (v, e) -> {}); + complete(stage, (v, e) -> { + if ( e != null ) + { + Throwables.propagate(e); + } + }); } protected <T, U> void complete(CompletionStage<T> stage, BiConsumer<? super T, Throwable> handler) http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java new file mode 100644 index 0000000..d709abe --- /dev/null +++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/TestMigrationManager.java @@ -0,0 +1,129 @@ +/** + * 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.migrations; + +import com.fasterxml.jackson.databind.ObjectMapper; +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.JacksonModelSerializer; +import org.apache.curator.x.async.modeled.ModelSpec; +import org.apache.curator.x.async.modeled.ModeledFramework; +import org.apache.curator.x.async.modeled.ZPath; +import org.apache.curator.x.async.modeled.migrations.models.ModelV1; +import org.apache.curator.x.async.modeled.migrations.models.ModelV2; +import org.apache.curator.x.async.modeled.migrations.models.ModelV3; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import java.io.IOException; +import java.util.Arrays; +import java.util.function.UnaryOperator; + +public class TestMigrationManager extends CompletableBaseClassForTests +{ + private AsyncCuratorFramework client; + private MigrationSet migrationSet; + private ModelSpec<ModelV1> v1Spec; + private ModelSpec<ModelV2> v2Spec; + private ModelSpec<ModelV3> v3Spec; + + @BeforeMethod + @Override + public void setup() throws Exception + { + super.setup(); + + CuratorFramework client = CuratorFrameworkFactory.newClient(server.getConnectString(), timing.session(), timing.connection(), new RetryOneTime(100)); + client.start(); + + this.client = AsyncCuratorFramework.wrap(client); + + ObjectMapper mapper = new ObjectMapper(); + UnaryOperator<byte[]> from1to2 = bytes -> { + try + { + ModelV1 v1 = mapper.readerFor(ModelV1.class).readValue(bytes); + ModelV2 v2 = new ModelV2(v1.getName(), 64); + return mapper.writeValueAsBytes(v2); + } + catch ( IOException e ) + { + throw new RuntimeException(e); + } + }; + + UnaryOperator<byte[]> from2to3 = bytes -> { + try + { + ModelV2 v2 = mapper.readerFor(ModelV2.class).readValue(bytes); + String[] nameParts = v2.getName().split("\\s"); + ModelV3 v3 = new ModelV3(nameParts[0], nameParts[1], v2.getAge()); + return mapper.writeValueAsBytes(v3); + } + catch ( IOException e ) + { + throw new RuntimeException(e); + } + }; + + ZPath modelPath = ZPath.parse("/test/it"); + + Migration m1 = Migration.build("1",1, from1to2); + Migration m2 = Migration.build("2",1, from2to3); + migrationSet = MigrationSet.build("1", modelPath, ZPath.parse("/metadata"), Arrays.asList(m1, m2)); + + v1Spec = ModelSpec.builder(modelPath, JacksonModelSerializer.build(ModelV1.class)).build(); + v2Spec = ModelSpec.builder(modelPath, JacksonModelSerializer.build(ModelV2.class)).build(); + v3Spec = ModelSpec.builder(modelPath, JacksonModelSerializer.build(ModelV3.class)).build(); + } + + @AfterMethod + @Override + public void teardown() throws Exception + { + CloseableUtils.closeQuietly(client.unwrap()); + super.teardown(); + } + + @Test + public void testBasic() throws Exception + { + ModeledFramework<ModelV1> v1Client = ModeledFramework.wrap(client, v1Spec); + ModelV1 v1 = new ModelV1("John Galt"); + complete(v1Client.child("1").set(v1)); + + MigrationManager manager = MigrationManager.builder(this.client, ZPath.parse("/locks"), JacksonModelSerializer.build(MetaData.class)) + .adding(migrationSet) + .build(); + + complete(manager.run()); + + ModeledFramework<ModelV3> v3Client = ModeledFramework.wrap(client, v3Spec); + complete(v3Client.child("1").read(), (m, e) -> { + Assert.assertEquals(m.getAge(), 64); + Assert.assertEquals(m.getFirstName(), "John"); + Assert.assertEquals(m.getLastName(), "Galt"); + }); + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java new file mode 100644 index 0000000..02b13b7 --- /dev/null +++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV1.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.curator.x.async.modeled.migrations.models; + +public class ModelV1 +{ + private final String name; + + public ModelV1() + { + this(""); + } + + public ModelV1(String name) + { + this.name = name; + } + + public String getName() + { + return name; + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java new file mode 100644 index 0000000..bd77a2e --- /dev/null +++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV2.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.curator.x.async.modeled.migrations.models; + +public class ModelV2 +{ + private final String name; + private final int age; + + public ModelV2() + { + this("", 0); + } + + public ModelV2(String name, int age) + { + this.name = name; + this.age = age; + } + + public String getName() + { + return name; + } + + public int getAge() + { + return age; + } +} http://git-wip-us.apache.org/repos/asf/curator/blob/4cd731c9/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java ---------------------------------------------------------------------- diff --git a/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java new file mode 100644 index 0000000..d4713b8 --- /dev/null +++ b/curator-x-async/src/test/java/org/apache/curator/x/async/modeled/migrations/models/ModelV3.java @@ -0,0 +1,53 @@ +/** + * 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.migrations.models; + +public class ModelV3 +{ + private final String firstName; + private final String lastName; + private final int age; + + public ModelV3() + { + this("", "", 0); + } + + public ModelV3(String firstName, String lastName, int age) + { + this.firstName = firstName; + this.lastName = lastName; + this.age = age; + } + + public String getFirstName() + { + return firstName; + } + + public String getLastName() + { + return lastName; + } + + public int getAge() + { + return age; + } +}
