Cleaned up IntentStores.

 * Removed HazelcastIntentStore
 * Moved SimpleIntentStore back to trivial bundle (and removed older version
   that was already in the trivial bundle)
 * Removed default methods from IntentStore interface

ONOS-1056

Change-Id: Id5e15f44e287f51cca3e0b12a85d49cb4a07a9d3
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 0eef07a..0a4fcb5 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
@@ -1,5 +1,5 @@
 /*
- * Copyright 2014 Open Networking Laboratory
+ * 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.
@@ -35,10 +35,9 @@
 import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.intent.IntentState.*;
 import static org.slf4j.LoggerFactory.getLogger;
 
-//TODO Note: this store will be removed
-
 @Component(immediate = true)
 @Service
 public class SimpleIntentStore
@@ -47,9 +46,21 @@
 
     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();
+
+    private IntentData copyData(IntentData original) {
+        if (original == null) {
+            return null;
+        }
+        IntentData result =
+                new IntentData(original.intent(), original.state(), original.version());
+
+        if (original.installables() != null) {
+            result.setInstallables(original.installables());
+        }
+        return result;
+    }
 
     @Activate
     public void activate() {
@@ -82,22 +93,110 @@
     @Override
     public List<Intent> getInstallableIntents(Key intentKey) {
         IntentData data = current.get(intentKey);
-        return (data != null) ? data.installables() : null;
+        if (data != null) {
+            return data.installables();
+        }
+        return null;
+    }
+
+
+    /**
+     * Determines whether an intent data update is allowed. The update must
+     * either have a higher version than the current data, or the state
+     * transition between two updates of the same version must be sane.
+     *
+     * @param currentData existing intent data in the store
+     * @param newData new intent data update proposal
+     * @return true if we can apply the update, otherwise false
+     */
+    private boolean isUpdateAcceptable(IntentData currentData, IntentData newData) {
+
+        if (currentData == null) {
+            return true;
+        } else if (currentData.version().compareTo(newData.version()) < 0) {
+            return true;
+        } else if (currentData.version().compareTo(newData.version()) > 0) {
+            return false;
+        }
+
+        // current and new data versions are the same
+        IntentState currentState = currentData.state();
+        IntentState newState = newData.state();
+
+        switch (newState) {
+            case INSTALLING:
+                if (currentState == INSTALLING) {
+                    return false;
+                }
+            // FALLTHROUGH
+            case INSTALLED:
+                if (currentState == INSTALLED) {
+                    return false;
+                } else if (currentState == WITHDRAWING || currentState == WITHDRAWN) {
+                    log.warn("Invalid state transition from {} to {} for intent {}",
+                             currentState, newState, newData.key());
+                    return false;
+                }
+                return true;
+
+            case WITHDRAWING:
+                if (currentState == WITHDRAWING) {
+                    return false;
+                }
+            // FALLTHOUGH
+            case WITHDRAWN:
+                if (currentState == WITHDRAWN) {
+                    return false;
+                } else if (currentState == INSTALLING || currentState == INSTALLED) {
+                    log.warn("Invalid state transition from {} to {} for intent {}",
+                             currentState, newState, newData.key());
+                    return false;
+                }
+                return true;
+
+
+            case FAILED:
+                if (currentState == FAILED) {
+                    return false;
+                }
+                return true;
+
+
+            case COMPILING:
+            case RECOMPILING:
+            case INSTALL_REQ:
+            case WITHDRAW_REQ:
+            default:
+                log.warn("Invalid state {} for intent {}", newState, newData.key());
+                return false;
+        }
     }
 
     @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);
+        synchronized (this) {
+            // TODO this could be refactored/cleaned up
+            IntentData currentData = current.get(newData.key());
+            IntentData pendingData = pending.get(newData.key());
+
+            if (isUpdateAcceptable(currentData, newData)) {
+                current.put(newData.key(), copyData(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());
+                }
+
+                notifyDelegateIfNotNull(IntentEvent.getEvent(newData));
+            }
         }
-        IntentData old = pending.get(newData.key());
-        if (old != null /* && FIXME version check */) {
-            pending.remove(newData.key());
+    }
+
+    private void notifyDelegateIfNotNull(IntentEvent event) {
+        if (event != null) {
+            notifyDelegate(event);
         }
     }
 
@@ -114,14 +213,44 @@
         return (data != null) ? data.intent() : null;
     }
 
+    @Override
+    public IntentData getIntentData(Key key) {
+        return copyData(current.get(key));
+    }
 
     @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));
+        if (data.version() == null) { // recompiled intents will already have a version
+            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);
+                notifyDelegateIfNotNull(IntentEvent.getEvent(data));
+            } else {
+                log.debug("IntentData {} is older than existing: {}",
+                          data, existingData);
+            }
+            //TODO consider also checking the current map at this point
+        }
     }
 
+    @Override
+    public boolean isMaster(Key intentKey) {
+        return true;
+    }
+
+    @Override
+    public Iterable<Intent> getPending() {
+        return pending.values().stream()
+                .map(IntentData::intent)
+                .collect(Collectors.toList());
+    }
 }