Passing current and previous config value in NetworkConfigEvent

Change-Id: I4ed16f17b5fc991594d1b83b6c0ffca7aa2130fa
diff --git a/core/api/src/main/java/org/onosproject/net/config/Config.java b/core/api/src/main/java/org/onosproject/net/config/Config.java
index 1e2ee12..58219d2 100644
--- a/core/api/src/main/java/org/onosproject/net/config/Config.java
+++ b/core/api/src/main/java/org/onosproject/net/config/Config.java
@@ -32,6 +32,7 @@
 import java.util.function.Function;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
 
 /**
  * Base abstraction of a configuration facade for a specific subject. Derived
@@ -77,17 +78,17 @@
      * @param key      configuration key
      * @param node     JSON node where configuration data is stored
      * @param mapper   JSON object mapper
-     * @param delegate delegate context
+     * @param delegate delegate context, or null for detached configs.
      */
-    public void init(S subject, String key, JsonNode node, ObjectMapper mapper,
+    public final void init(S subject, String key, JsonNode node, ObjectMapper mapper,
                      ConfigApplyDelegate delegate) {
-        this.subject = checkNotNull(subject);
+        this.subject = checkNotNull(subject, "Subject cannot be null");
         this.key = key;
-        this.node = checkNotNull(node);
+        this.node = checkNotNull(node, "Node cannot be null");
         this.object = node instanceof ObjectNode ? (ObjectNode) node : null;
         this.array = node instanceof ArrayNode ? (ArrayNode) node : null;
-        this.mapper = checkNotNull(mapper);
-        this.delegate = checkNotNull(delegate);
+        this.mapper = checkNotNull(mapper, "Mapper cannot be null");
+        this.delegate = delegate;
     }
 
     /**
@@ -144,12 +145,14 @@
 
     /**
      * Applies any configuration changes made via this configuration.
+     *
+     * Not effective for detached configs.
      */
     public void apply() {
+        checkState(delegate != null, "Cannot apply detached config");
         delegate.onApply(this);
     }
 
-
     // Miscellaneous helpers for interacting with JSON
 
     /**
diff --git a/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java b/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java
index ee9cead..433eb9d 100644
--- a/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java
+++ b/core/api/src/main/java/org/onosproject/net/config/NetworkConfigEvent.java
@@ -15,14 +15,21 @@
  */
 package org.onosproject.net.config;
 
+import org.joda.time.LocalDateTime;
 import org.onosproject.event.AbstractEvent;
 
+import java.util.Optional;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
 /**
  * Describes network configuration event.
  */
 public class NetworkConfigEvent extends AbstractEvent<NetworkConfigEvent.Type, Object> {
 
     private final Class configClass;
+    private final Config config;
+    private final Config prevConfig;
 
     /**
      * Type of network configuration events.
@@ -65,6 +72,8 @@
     public NetworkConfigEvent(Type type, Object subject, Class configClass) {
         super(type, subject);
         this.configClass = configClass;
+        this.config = null;
+        this.prevConfig = null;
     }
 
     /**
@@ -78,6 +87,26 @@
     public NetworkConfigEvent(Type type, Object subject, Class configClass, long time) {
         super(type, subject, time);
         this.configClass = configClass;
+        this.config = null;
+        this.prevConfig = null;
+    }
+
+    /**
+     * Creates an event of a given type and for the specified subject,
+     * previous config and time.
+     *
+     * @param type        device event type
+     * @param subject     event subject
+     * @param configClass configuration class
+     * @param config      current config
+     * @param prevConfig  previous config
+     */
+    public NetworkConfigEvent(Type type, Object subject, Config config,
+            Config prevConfig, Class configClass) {
+        super(type, subject);
+        this.configClass = configClass;
+        this.config = config;
+        this.prevConfig = prevConfig;
     }
 
     /**
@@ -89,4 +118,34 @@
         return configClass;
     }
 
+    /**
+     * Returns current config.
+     *
+     * @return current config; value presents only when the type is
+     *         CONFIG_ADDED or CONFIG_UPDATED
+     */
+    public Optional<Config> config() {
+        return (config != null) ? Optional.of(config) : Optional.empty();
+    }
+
+    /**
+     * Returns previous config.
+     *
+     * @return previous config; value presents only when the type is
+     *         CONFIG_UPDATED or CONFIG_REMOVED
+     */
+    public Optional<Config> prevConfig() {
+        return (prevConfig != null) ? Optional.of(prevConfig) : Optional.empty();
+    }
+
+    @Override
+    public String toString() {
+        return toStringHelper(this)
+                .add("time", new LocalDateTime(time()))
+                .add("type", type())
+                .add("config", config())
+                .add("prevConfig", prevConfig())
+                .add("configClass", configClass())
+                .toString();
+    }
 }
diff --git a/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java b/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
index d0a9920..4a31aff 100644
--- a/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
+++ b/core/store/dist/src/main/java/org/onosproject/store/config/impl/DistributedNetworkConfigStore.java
@@ -267,6 +267,33 @@
         return null;
     }
 
+    /**
+     * Produces a detached config from the specified subject, config class and
+     * raw JSON.
+     *
+     * A detached config can no longer be applied. This should be used only for
+     * passing the config object in the NetworkConfigEvent.
+     *
+     * @param subject     config subject
+     * @param configClass config class
+     * @param json        raw JSON data
+     * @return config object or null of no factory found or if the specified
+     * JSON is null
+     */
+    @SuppressWarnings("unchecked")
+    private Config createDetachedConfig(Object subject,
+            Class configClass, JsonNode json) {
+        if (json != null) {
+            ConfigFactory factory = factoriesByConfig.get(configClass.getName());
+            if (factory != null) {
+                Config config = factory.createConfig();
+                config.init(subject, factory.configKey(), json, mapper, null);
+                return config;
+            }
+        }
+        return null;
+    }
+
 
     // Auxiliary delegate to receive notifications about changes applied to
     // the network configuration - by the apps.
@@ -336,23 +363,35 @@
                 return;
             }
 
-            NetworkConfigEvent.Type type;
-            switch (event.type()) {
-                case INSERT:
-                    type = CONFIG_ADDED;
-                    break;
-                case UPDATE:
-                    type = CONFIG_UPDATED;
-                    break;
-                case REMOVE:
-                default:
-                    type = CONFIG_REMOVED;
-                    break;
-            }
             ConfigFactory factory = factoriesByConfig.get(event.key().configClass);
             if (factory != null) {
+                Object subject = event.key().subject;
+                Class configClass = factory.configClass();
+                Versioned<JsonNode> newValue = event.newValue();
+                Versioned<JsonNode> oldValue = event.oldValue();
+
+                Config config = (newValue != null) ?
+                        createDetachedConfig(subject, configClass, newValue.value()) :
+                        null;
+                Config prevConfig = (oldValue != null) ?
+                        createDetachedConfig(subject, configClass, oldValue.value()) :
+                        null;
+
+                NetworkConfigEvent.Type type;
+                switch (event.type()) {
+                    case INSERT:
+                        type = CONFIG_ADDED;
+                        break;
+                    case UPDATE:
+                        type = CONFIG_UPDATED;
+                        break;
+                    case REMOVE:
+                    default:
+                        type = CONFIG_REMOVED;
+                        break;
+                }
                 notifyDelegate(new NetworkConfigEvent(type, event.key().subject,
-                                                      factory.configClass()));
+                        config, prevConfig, factory.configClass()));
             }
         }
     }