Clean up a few SSM-translate things, incl config validation

Change-Id: I5308fd8a73088ea6a522f22642ee834ac8a7a446
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSnoop.java b/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSnoop.java
index 0393ed9..52760a6 100644
--- a/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSnoop.java
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSnoop.java
@@ -84,7 +84,6 @@
 @Component(immediate = true)
 public class IgmpSnoop {
 
-
     private final Logger log = getLogger(getClass());
 
     private static final String DEST_MAC = "01:00:5E:00:00:01";
@@ -144,6 +143,9 @@
     private static final Class<AccessDeviceConfig> CONFIG_CLASS =
             AccessDeviceConfig.class;
 
+    private static final Class<IgmpSsmTranslateConfig> SSM_TRANSLATE_CONFIG_CLASS =
+            IgmpSsmTranslateConfig.class;
+
     private ConfigFactory<DeviceId, AccessDeviceConfig> configFactory =
             new ConfigFactory<DeviceId, AccessDeviceConfig>(
                     SubjectFactories.DEVICE_SUBJECT_FACTORY, CONFIG_CLASS, "accessDevice") {
@@ -155,7 +157,7 @@
 
     private ConfigFactory<ApplicationId, IgmpSsmTranslateConfig> ssmTranslateConfigFactory =
             new ConfigFactory<ApplicationId, IgmpSsmTranslateConfig>(
-                    SubjectFactories.APP_SUBJECT_FACTORY, IgmpSsmTranslateConfig.class, "ssmTranslate", true) {
+                    SubjectFactories.APP_SUBJECT_FACTORY, SSM_TRANSLATE_CONFIG_CLASS, "ssmTranslate", true) {
                 @Override
                 public IgmpSsmTranslateConfig createConfig() {
                     return new IgmpSsmTranslateConfig();
@@ -466,9 +468,25 @@
                             provisionDefaultFlows((DeviceId) event.subject());
                         }
                     }
+                    if (event.configClass().equals(SSM_TRANSLATE_CONFIG_CLASS)) {
+                        IgmpSsmTranslateConfig config =
+                                networkConfig.getConfig((ApplicationId) event.subject(),
+                                        SSM_TRANSLATE_CONFIG_CLASS);
+
+                        if (config != null) {
+                            ssmTranslateTable.clear();
+                            config.getSsmTranslations().forEach(
+                                    route -> ssmTranslateTable.put(route.group(), route.source()));
+                        }
+                    }
                     break;
+                case CONFIG_REGISTERED:
                 case CONFIG_UNREGISTERED:
+                    break;
                 case CONFIG_REMOVED:
+                    if (event.configClass().equals(SSM_TRANSLATE_CONFIG_CLASS)) {
+                        ssmTranslateTable.clear();
+                    }
                 default:
                     break;
             }
diff --git a/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSsmTranslateConfig.java b/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSsmTranslateConfig.java
index ea2d983..f69d76a 100644
--- a/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSsmTranslateConfig.java
+++ b/apps/igmp/src/main/java/org/onosproject/igmp/IgmpSsmTranslateConfig.java
@@ -17,6 +17,7 @@
 package org.onosproject.igmp;
 
 import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.onlab.packet.IpAddress;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.net.config.Config;
@@ -26,13 +27,34 @@
 import java.util.List;
 
 /**
- * Created by jono on 2/16/16.
+ * IGMP SSM translate configuration.
  */
 public class IgmpSsmTranslateConfig extends Config<ApplicationId> {
-    private static final String SSM_TRANSLATE = "ssmTranslate";
+
     private static final String SOURCE = "source";
     private static final String GROUP = "group";
 
+    @Override
+    public boolean isValid() {
+        for (JsonNode node : array) {
+            if (!hasOnlyFields((ObjectNode) node, SOURCE, GROUP)) {
+                return false;
+            }
+
+            if (!(isIpAddress((ObjectNode) node, SOURCE, FieldPresence.MANDATORY) &&
+                    isIpAddress((ObjectNode) node, GROUP, FieldPresence.MANDATORY))) {
+                return false;
+            }
+
+        }
+        return true;
+    }
+
+    /**
+     * Gets the list of SSM translations.
+     *
+     * @return SSM translations
+     */
     public List<McastRoute> getSsmTranslations() {
         List<McastRoute> translations = new ArrayList();
         for (JsonNode node : array) {
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 58219d2..0119e07 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
@@ -393,8 +393,20 @@
      * @return true if all allowedFields are present; false otherwise
      */
     protected boolean hasOnlyFields(String... allowedFields) {
+        return hasOnlyFields(object, allowedFields);
+    }
+
+    /**
+     * Indicates whether only the specified fields are present in a particular
+     * JSON object.
+     *
+     * @param node node whose fields to check
+     * @param allowedFields allowed field names
+     * @return true if all allowedFields are present; false otherwise
+     */
+    protected boolean hasOnlyFields(ObjectNode node, String... allowedFields) {
         Set<String> fields = ImmutableSet.copyOf(allowedFields);
-        return !Iterators.any(object.fieldNames(), f -> !fields.contains(f));
+        return !Iterators.any(node.fieldNames(), f -> !fields.contains(f));
     }
 
     /**
@@ -420,9 +432,23 @@
      * @throws IllegalArgumentException if field is present, but not valid IP
      */
     protected boolean isIpAddress(String field, FieldPresence presence) {
-        JsonNode node = object.path(field);
-        return isValid(node, presence, node.isTextual() &&
-                IpAddress.valueOf(node.asText()) != null);
+        return isIpAddress(object, field, presence);
+    }
+
+    /**
+     * Indicates whether the specified field of a particular node holds a valid
+     * IP address.
+     *
+     * @param node     node from whom to access the field
+     * @param field    JSON field name
+     * @param presence specifies if field is optional or mandatory
+     * @return true if valid; false otherwise
+     * @throws IllegalArgumentException if field is present, but not valid IP
+     */
+    protected boolean isIpAddress(ObjectNode node, String field, FieldPresence presence) {
+        JsonNode innerNode = node.path(field);
+        return isValid(innerNode, presence, innerNode.isTextual() &&
+                IpAddress.valueOf(innerNode.asText()) != null);
     }
 
     /**