Adding version stamping/checking to SimpleIntentStore

Change-Id: I08c0bf5e0f5a89275a72fa0900e52ca996942b79
diff --git a/core/api/src/main/java/org/onosproject/net/intent/IntentData.java b/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
index 3eea0d5..0abfd12 100644
--- a/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
+++ b/core/api/src/main/java/org/onosproject/net/intent/IntentData.java
@@ -56,10 +56,28 @@
         return intent.key();
     }
 
+    public Timestamp version() {
+        return version;
+    }
+
     public void setState(IntentState newState) {
         this.state = newState;
     }
 
+    /**
+     * Sets the version for this intent data.
+     * <p>
+     * The store should call this method only once when the IntentData is
+     * first passed into the pending map. Ideally, an IntentData is timestamped
+     * on the same thread that the called used to submit the intents.
+     * </p>
+     *
+     * @param version the version/timestamp for this intent data
+     */
+    public void setVersion(Timestamp version) {
+        this.version = version;
+    }
+
     public void setInstallables(List<Intent> installables) {
         this.installables = ImmutableList.copyOf(installables);
     }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/impl/SystemClockTimestamp.java b/core/store/dist/src/main/java/org/onosproject/store/impl/SystemClockTimestamp.java
new file mode 100644
index 0000000..dc8817c
--- /dev/null
+++ b/core/store/dist/src/main/java/org/onosproject/store/impl/SystemClockTimestamp.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 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.impl;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.collect.ComparisonChain;
+import org.onosproject.store.Timestamp;
+
+import java.util.Objects;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+/**
+ * A Timestamp that derives its value from the system clock time (in ns)
+ * on the controller where it is generated.
+ */
+public class SystemClockTimestamp implements Timestamp {
+
+    private final long unixTimestamp;
+
+    public SystemClockTimestamp() {
+        unixTimestamp = System.nanoTime();
+    }
+
+    @Override
+    public int compareTo(Timestamp o) {
+        checkArgument(o instanceof SystemClockTimestamp,
+                "Must be SystemClockTimestamp", o);
+        SystemClockTimestamp that = (SystemClockTimestamp) o;
+
+        return ComparisonChain.start()
+                .compare(this.unixTimestamp, that.unixTimestamp)
+                .result();
+    }
+    @Override
+    public int hashCode() {
+        return Objects.hash(unixTimestamp);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (!(obj instanceof SystemClockTimestamp)) {
+            return false;
+        }
+        SystemClockTimestamp that = (SystemClockTimestamp) obj;
+        return Objects.equals(this.unixTimestamp, that.unixTimestamp);
+    }
+
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(getClass())
+                    .add("unixTimestamp", unixTimestamp)
+                    .toString();
+    }
+
+    public long systemTimestamp() {
+        return unixTimestamp;
+    }
+}
diff --git a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/DistributedIntentStore.java b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/DistributedIntentStore.java
index 0d8c02a..8d56098 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/DistributedIntentStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/DistributedIntentStore.java
@@ -71,6 +71,8 @@
 import static org.onosproject.net.intent.IntentState.WITHDRAWN;
 import static org.slf4j.LoggerFactory.getLogger;
 
+//TODO Note: this store will be removed
+
 @Component(immediate = true, enabled = false)
 @Service
 public class DistributedIntentStore
diff --git a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/HazelcastIntentStore.java b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/HazelcastIntentStore.java
index 8e91d61..e66d7ca 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/HazelcastIntentStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/HazelcastIntentStore.java
@@ -69,6 +69,8 @@
 import static org.onosproject.net.intent.IntentState.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
+//TODO Note: this store will be removed
+
 @Component(immediate = true, enabled = false)
 @Service
 public class HazelcastIntentStore
diff --git a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/SimpleIntentStore.java b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/SimpleIntentStore.java
index 0d9f26e..c1e6103 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/intent/impl/SimpleIntentStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/intent/impl/SimpleIntentStore.java
@@ -30,6 +30,7 @@
 import org.onosproject.net.intent.IntentStoreDelegate;
 import org.onosproject.net.intent.Key;
 import org.onosproject.store.AbstractStore;
+import org.onosproject.store.impl.SystemClockTimestamp;
 import org.slf4j.Logger;
 
 import java.util.List;
@@ -39,6 +40,8 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
+//TODO Note: this store will be removed once the GossipIntentStore is stable
+
 @Component(immediate = true)
 @Service
 //FIXME remove this
@@ -48,9 +51,8 @@
 
     private final Logger log = getLogger(getClass());
 
-    // current state maps FIXME.. make this a IntentData map
     private final Map<Key, IntentData> current = Maps.newConcurrentMap();
-    private final Map<Key, IntentData> pending = Maps.newConcurrentMap(); //String is "key"
+    private final Map<Key, IntentData> pending = Maps.newConcurrentMap();
 
     @Activate
     public void activate() {
@@ -160,17 +162,30 @@
 
     @Override
     public void write(IntentData newData) {
-        //FIXME need to compare the versions
-        current.put(newData.key(), newData);
-        try {
-            notifyDelegate(IntentEvent.getEvent(newData));
-        } catch (IllegalArgumentException e) {
-            //no-op
-            log.trace("ignore this exception: {}", e);
-        }
-        IntentData old = pending.get(newData.key());
-        if (old != null /* && FIXME version check */) {
-            pending.remove(newData.key());
+        synchronized (this) {
+            // TODO this could be refactored/cleaned up
+            IntentData currentData = current.get(newData.key());
+            IntentData pendingData = pending.get(newData.key());
+            if (currentData == null ||
+                    // current version is less than or equal to newData's
+                    // Note: current and newData's versions will be equal for state updates
+                    currentData.version().compareTo(newData.version()) <= 0) {
+                current.put(newData.key(), newData);
+
+                if (pendingData != null
+                        // pendingData version is less than or equal to newData's
+                        // Note: a new update for this key could be pending (it's version will be greater)
+                        && pendingData.version().compareTo(newData.version()) <= 0) {
+                    pending.remove(newData.key());
+                }
+
+                try {
+                    notifyDelegate(IntentEvent.getEvent(newData));
+                } catch (IllegalArgumentException e) {
+                    //no-op
+                    log.trace("ignore this exception: {}", e);
+                }
+            }
         }
     }
 
@@ -187,14 +202,26 @@
         return (data != null) ? data.intent() : null;
     }
 
-
     @Override
     public void addPending(IntentData data) {
-        //FIXME need to compare versions
-        pending.put(data.key(), data);
-        checkNotNull(delegate, "Store delegate is not set")
-                .process(data);
-        notifyDelegate(IntentEvent.getEvent(data));
+        data.setVersion(new SystemClockTimestamp());
+        synchronized (this) {
+            IntentData existingData = pending.get(data.key());
+            if (existingData == null ||
+                    // existing version is strictly less than data's version
+                    // Note: if they are equal, we already have the update
+                    // TODO maybe we should still make this <= to be safe?
+                    existingData.version().compareTo(data.version()) < 0) {
+                pending.put(data.key(), data);
+                checkNotNull(delegate, "Store delegate is not set")
+                        .process(data);
+                notifyDelegate(IntentEvent.getEvent(data));
+            } else {
+                log.debug("IntentData {} is older than existing: {}",
+                          data, existingData);
+            }
+            //TODO consider also checking the current map at this point
+        }
     }
 
 
diff --git a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleIntentStore.java b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleIntentStore.java
index 2831951..906b942 100644
--- a/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleIntentStore.java
+++ b/core/store/trivial/src/main/java/org/onosproject/store/trivial/impl/SimpleIntentStore.java
@@ -39,6 +39,8 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
+//TODO Note: this store will be removed
+
 @Component(immediate = true)
 @Service
 public class SimpleIntentStore