[ONOS-6342] Refactor transaction architecture to support a shared cache for transactional primitives

Change-Id: I2a17965100895f5aa4d2202028047bb980c11d26
diff --git a/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java b/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java
index 3b12021..e04f10e 100644
--- a/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java
+++ b/core/api/src/main/java/org/onosproject/store/primitives/MapUpdate.java
@@ -19,6 +19,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
+import java.util.Objects;
 import java.util.function.Function;
 
 import org.onlab.util.ByteArraySizeHashPrinter;
@@ -42,6 +43,7 @@
          * Insert/Update entry without any checks.
          */
         PUT,
+
         /**
          * Insert an entry iff there is no existing entry for that key.
          */
@@ -73,7 +75,6 @@
         REMOVE_IF_VALUE_MATCH,
     }
 
-    private String mapName;
     private Type type;
     private K key;
     private V value;
@@ -81,15 +82,6 @@
     private long currentVersion = -1;
 
     /**
-     * Returns the name of the map.
-     *
-     * @return map name
-     */
-    public String mapName() {
-        return mapName;
-    }
-
-    /**
      * Returns the type of update operation.
      * @return type of update.
      */
@@ -140,7 +132,6 @@
      */
     public <S, T> MapUpdate<S, T> map(Function<K, S> keyMapper, Function<V, T> valueMapper) {
         return MapUpdate.<S, T>newBuilder()
-                .withMapName(mapName)
                 .withType(type)
                 .withKey(keyMapper.apply(key))
                 .withValue(value == null ? null : valueMapper.apply(value))
@@ -150,9 +141,26 @@
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(type, key, value, currentValue, currentVersion);
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof MapUpdate) {
+            MapUpdate that = (MapUpdate) object;
+            return this.type == that.type
+                    && Objects.equals(this.key, that.key)
+                    && Objects.equals(this.value, that.value)
+                    && Objects.equals(this.currentValue, that.currentValue)
+                    && Objects.equals(this.currentVersion, that.currentVersion);
+        }
+        return false;
+    }
+
+    @Override
     public String toString() {
         return MoreObjects.toStringHelper(this)
-            .add("mapName", mapName)
             .add("type", type)
             .add("key", key)
             .add("value", value instanceof byte[] ? new ByteArraySizeHashPrinter((byte[]) value) : value)
@@ -187,11 +195,6 @@
             return update;
         }
 
-        public Builder<K, V> withMapName(String name) {
-            update.mapName = checkNotNull(name, "name cannot be null");
-            return this;
-        }
-
         public Builder<K, V> withType(Type type) {
             update.type = checkNotNull(type, "type cannot be null");
             return this;
diff --git a/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java b/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
index 8bfd952..0704853 100644
--- a/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
+++ b/core/api/src/main/java/org/onosproject/store/service/AsyncConsistentMap.java
@@ -17,8 +17,8 @@
 package org.onosproject.store.service;
 
 import java.util.Collection;
-import java.util.Objects;
 import java.util.Map.Entry;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
@@ -26,10 +26,9 @@
 import java.util.function.Function;
 import java.util.function.Predicate;
 
-import org.onosproject.store.primitives.DefaultConsistentMap;
-import org.onosproject.store.primitives.TransactionId;
-
 import com.google.common.util.concurrent.MoreExecutors;
+import org.onosproject.store.primitives.DefaultConsistentMap;
+import org.onosproject.store.primitives.MapUpdate;
 
 /**
  * A distributed, strongly consistent map whose methods are all executed asynchronously.
@@ -55,7 +54,7 @@
  * the returned future will be {@link CompletableFuture#complete completed} when the
  * operation finishes.
  */
-public interface AsyncConsistentMap<K, V> extends DistributedPrimitive {
+public interface AsyncConsistentMap<K, V> extends DistributedPrimitive, Transactional<MapUpdate<K, V>> {
 
     @Override
     default DistributedPrimitive.Type primitiveType() {
@@ -346,36 +345,6 @@
     CompletableFuture<Void> removeListener(MapEventListener<K, V> listener);
 
     /**
-     * Prepares a transaction for commitment.
-     * @param transaction transaction
-     * @return {@code true} if prepare is successful and transaction is ready to be committed;
-     * {@code false} otherwise
-     */
-    CompletableFuture<Boolean> prepare(MapTransaction<K, V> transaction);
-
-    /**
-     * Commits a previously prepared transaction.
-     * @param transactionId transaction identifier
-     * @return future that will be completed when the operation finishes
-     */
-    CompletableFuture<Void> commit(TransactionId transactionId);
-
-    /**
-     * Aborts a previously prepared transaction.
-     * @param transactionId transaction identifier
-     * @return future that will be completed when the operation finishes
-     */
-    CompletableFuture<Void> rollback(TransactionId transactionId);
-
-    /**
-     * Prepares a transaction and commits it in one go.
-     * @param transaction transaction
-     * @return {@code true} if operation is successful and updates are committed
-     * {@code false} otherwise
-     */
-    CompletableFuture<Boolean> prepareAndCommit(MapTransaction<K, V> transaction);
-
-    /**
      * Returns a new {@link ConsistentMap} that is backed by this instance.
      *
      * @return new {@code ConsistentMap} instance
diff --git a/core/api/src/main/java/org/onosproject/store/service/MapTransaction.java b/core/api/src/main/java/org/onosproject/store/service/MapTransaction.java
deleted file mode 100644
index acedf17..0000000
--- a/core/api/src/main/java/org/onosproject/store/service/MapTransaction.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright 2016-present Open Networking Laboratory
- *
- * Licensed 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.onosproject.store.service;
-
-import java.util.List;
-import java.util.function.Function;
-
-import org.onosproject.store.primitives.MapUpdate;
-import org.onosproject.store.primitives.TransactionId;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-
-/**
- * Collection of map updates to be committed atomically.
- *
- * @param <K> key type
- * @param <V> value type
- */
-public class MapTransaction<K, V> {
-
-    private final TransactionId transactionId;
-    private final List<MapUpdate<K, V>> updates;
-
-    public MapTransaction(TransactionId transactionId, List<MapUpdate<K, V>> updates) {
-        this.transactionId = transactionId;
-        this.updates = ImmutableList.copyOf(updates);
-    }
-
-    /**
-     * Returns the transaction identifier.
-     *
-     * @return transaction id
-     */
-    public TransactionId transactionId() {
-        return transactionId;
-    }
-
-    /**
-     * Returns the list of map updates.
-     *
-     * @return map updates
-     */
-    public List<MapUpdate<K, V>> updates() {
-        return updates;
-    }
-
-    @Override
-    public String toString() {
-        return MoreObjects.toStringHelper(getClass())
-                .add("transactionId", transactionId)
-                .add("updates", updates)
-                .toString();
-    }
-
-    /**
-     * Maps this instance to another {@code MapTransaction} with different key and value types.
-     *
-     * @param keyMapper function for mapping key types
-     * @param valueMapper function for mapping value types
-     * @return newly typed instance
-     *
-     * @param <S> key type of returned instance
-     * @param <T> value type of returned instance
-     */
-    public <S, T> MapTransaction<S, T> map(Function<K, S> keyMapper, Function<V, T> valueMapper) {
-        return new MapTransaction<>(transactionId, Lists.transform(updates, u -> u.map(keyMapper, valueMapper)));
-    }
-}
diff --git a/core/api/src/main/java/org/onosproject/store/service/TransactionLog.java b/core/api/src/main/java/org/onosproject/store/service/TransactionLog.java
new file mode 100644
index 0000000..c367f5d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/store/service/TransactionLog.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed 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.onosproject.store.service;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.onosproject.store.primitives.TransactionId;
+
+/**
+ * Collection of transaction updates to be applied atomically.
+ *
+ * @param <T> log record type
+ */
+public class TransactionLog<T> {
+    private final TransactionId transactionId;
+    private final List<T> records;
+
+    public TransactionLog(TransactionId transactionId, List<T> records) {
+        this.transactionId = transactionId;
+        this.records = ImmutableList.copyOf(records);
+    }
+
+    /**
+     * Returns the transaction identifier.
+     *
+     * @return transaction id
+     */
+    public TransactionId transactionId() {
+        return transactionId;
+    }
+
+    /**
+     * Returns the list of transaction log records.
+     *
+     * @return a list of transaction log records
+     */
+    public List<T> records() {
+        return records;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object instanceof TransactionLog) {
+            TransactionLog that = (TransactionLog) object;
+            return this.transactionId.equals(that.transactionId)
+                    && this.records.equals(that.records);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(transactionId, records);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("transactionId", transactionId)
+                .add("records", records)
+                .toString();
+    }
+
+    /**
+     * Maps this instance to another {@code MapTransaction} with different key and value types.
+     *
+     * @param mapper function for mapping record types
+     * @return newly typed instance
+     *
+     * @param <U> record type of returned instance
+     */
+    public <U> TransactionLog<U> map(Function<T, U> mapper) {
+        return new TransactionLog<>(transactionId, Lists.transform(records, mapper::apply));
+    }
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/store/service/Transactional.java b/core/api/src/main/java/org/onosproject/store/service/Transactional.java
new file mode 100644
index 0000000..a6e1fbd
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/store/service/Transactional.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed 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.onosproject.store.service;
+
+import java.util.concurrent.CompletableFuture;
+
+import org.onosproject.store.primitives.TransactionId;
+
+/**
+ * Interface for transactional primitives.
+ */
+public interface Transactional<T> {
+
+    /**
+     * Begins the transaction.
+     *
+     * @param transactionId the transaction identifier for the transaction to begin
+     * @return a completable future to be completed with the lock version
+     */
+    CompletableFuture<Version> begin(TransactionId transactionId);
+
+    /**
+     * Prepares a transaction for commitment.
+     *
+     * @param transactionLog transaction log
+     * @return {@code true} if prepare is successful and transaction is ready to be committed
+     * {@code false} otherwise
+     */
+    CompletableFuture<Boolean> prepare(TransactionLog<T> transactionLog);
+
+    /**
+     * Prepares and commits a transaction.
+     *
+     * @param transactionLog transaction log
+     * @return {@code true} if prepare is successful and transaction was committed
+     * {@code false} otherwise
+     */
+    CompletableFuture<Boolean> prepareAndCommit(TransactionLog<T> transactionLog);
+
+    /**
+     * Commits a previously prepared transaction and unlocks the object.
+     *
+     * @param transactionId transaction identifier
+     * @return future that will be completed when the operation finishes
+     */
+    CompletableFuture<Void> commit(TransactionId transactionId);
+
+    /**
+     * Aborts a previously prepared transaction and unlocks the object.
+     *
+     * @param transactionId transaction identifier
+     * @return future that will be completed when the operation finishes
+     */
+    CompletableFuture<Void> rollback(TransactionId transactionId);
+
+}
\ No newline at end of file
diff --git a/core/api/src/main/java/org/onosproject/store/service/Version.java b/core/api/src/main/java/org/onosproject/store/service/Version.java
new file mode 100644
index 0000000..58a1d3d
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/store/service/Version.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed 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.onosproject.store.service;
+
+import java.util.Objects;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import org.onosproject.store.Timestamp;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * Logical timestamp for versions.
+ * <p>
+ * The version is a logical timestamp that represents a point in logical time at which an event occurs.
+ * This is used in both pessimistic and optimistic locking protocols to ensure that the state of a shared resource
+ * has not changed at the end of a transaction.
+ */
+public class Version implements Timestamp {
+    private final long version;
+
+    public Version(long version) {
+        this.version = version;
+    }
+
+    @Override
+    public int compareTo(Timestamp o) {
+        checkArgument(o instanceof Version,
+                "Must be LockVersion", o);
+        Version that = (Version) o;
+
+        return ComparisonChain.start()
+                .compare(this.version, that.version)
+                .result();
+    }
+
+    @Override
+    public int hashCode() {
+        return Long.hashCode(version);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof Version)) {
+            return false;
+        }
+        Version that = (Version) obj;
+        return Objects.equals(this.version, that.version);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                .add("version", version)
+                .toString();
+    }
+
+    /**
+     * Returns the lock version.
+     *
+     * @return the lock version
+     */
+    public long value() {
+        return this.version;
+    }
+}
\ No newline at end of file
diff --git a/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java b/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java
index 6fecd8c..53cc160 100644
--- a/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java
+++ b/core/api/src/test/java/org/onosproject/store/primitives/MapUpdateTest.java
@@ -33,7 +33,6 @@
             .withValue("2".getBytes())
             .withCurrentVersion(3)
             .withKey("4")
-            .withMapName("5")
             .withType(MapUpdate.Type.PUT)
             .build();
 
@@ -42,7 +41,6 @@
             .withValue("2".getBytes())
             .withCurrentVersion(3)
             .withKey("4")
-            .withMapName("5")
             .withType(MapUpdate.Type.REMOVE)
             .build();
 
@@ -51,7 +49,6 @@
             .withValue("2".getBytes())
             .withCurrentVersion(3)
             .withKey("4")
-            .withMapName("5")
             .withType(MapUpdate.Type.REMOVE_IF_VALUE_MATCH)
             .build();
 
@@ -60,7 +57,6 @@
             .withValue("2".getBytes())
             .withCurrentVersion(3)
             .withKey("4")
-            .withMapName("5")
             .withType(MapUpdate.Type.REMOVE_IF_VERSION_MATCH)
             .build();
 
@@ -69,7 +65,6 @@
             .withValue("2".getBytes())
             .withCurrentVersion(3)
             .withKey("4")
-            .withMapName("5")
             .withType(MapUpdate.Type.PUT_IF_VALUE_MATCH)
             .build();
 
@@ -78,7 +73,6 @@
             .withValue("2".getBytes())
             .withCurrentVersion(3)
             .withKey("4")
-            .withMapName("5")
             .withType(MapUpdate.Type.PUT_IF_VERSION_MATCH)
             .build();
 
@@ -91,7 +85,6 @@
         assertThat(stats1.value(), is("2".getBytes()));
         assertThat(stats1.currentVersion(), is(3L));
         assertThat(stats1.key(), is("4"));
-        assertThat(stats1.mapName(), is("5"));
         assertThat(stats1.type(), is(MapUpdate.Type.PUT));
     }
 
diff --git a/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java b/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java
index c9161a3..90b4f51 100644
--- a/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java
+++ b/core/api/src/test/java/org/onosproject/store/service/AsyncConsistentMapAdapter.java
@@ -24,6 +24,7 @@
 import java.util.function.BiFunction;
 import java.util.function.Predicate;
 
+import org.onosproject.store.primitives.MapUpdate;
 import org.onosproject.store.primitives.TransactionId;
 
 /**
@@ -143,7 +144,17 @@
     }
 
     @Override
-    public CompletableFuture<Boolean> prepare(MapTransaction<K, V> transaction) {
+    public CompletableFuture<Version> begin(TransactionId transactionId) {
+        return null;
+    }
+
+    @Override
+    public CompletableFuture<Boolean> prepare(TransactionLog<MapUpdate<K, V>> transactionLog) {
+        return null;
+    }
+
+    @Override
+    public CompletableFuture<Boolean> prepareAndCommit(TransactionLog<MapUpdate<K, V>> transactionLog) {
         return null;
     }
 
@@ -156,10 +167,5 @@
     public CompletableFuture<Void> rollback(TransactionId transactionId) {
         return null;
     }
-
-    @Override
-    public CompletableFuture<Boolean> prepareAndCommit(MapTransaction<K, V> transaction) {
-        return null;
-    }
 }
 
diff --git a/core/api/src/test/java/org/onosproject/store/service/VersionTest.java b/core/api/src/test/java/org/onosproject/store/service/VersionTest.java
new file mode 100644
index 0000000..1495ec3
--- /dev/null
+++ b/core/api/src/test/java/org/onosproject/store/service/VersionTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed 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.onosproject.store.service;
+
+import org.junit.Test;
+
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+/**
+ * Version test.
+ */
+public class VersionTest {
+
+    @Test
+    public void testVersion() {
+        Version version1 = new Version(1);
+        Version version2 = new Version(1);
+        assertTrue(version1.equals(version2));
+        assertTrue(version1.hashCode() == version2.hashCode());
+        assertTrue(version1.value() == version2.value());
+
+        Version version3 = new Version(2);
+        assertFalse(version1.equals(version3));
+        assertFalse(version1.hashCode() == version3.hashCode());
+        assertFalse(version1.value() == version3.value());
+    }
+
+}