CORD Subscriber GUI - Plumbed through the parameter change per function per user.

Change-Id: I9b8eb677f606fd75f70366cec7f5b4993d188ab1
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/CordModelCache.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/CordModelCache.java
index c71477f..10b7318 100644
--- a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/CordModelCache.java
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/CordModelCache.java
@@ -28,10 +28,12 @@
 import org.onosproject.cord.gui.model.UserFactory;
 import org.onosproject.cord.gui.model.XosFunction;
 import org.onosproject.cord.gui.model.XosFunctionDescriptor;
-import org.onosproject.cord.gui.model.XosFunctionFactory;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * In memory cache of the model of the subscriber's account.
@@ -50,14 +52,16 @@
     private static final String MAC_4 = "010203040509";
 
     private Bundle currentBundle;
-    private final List<SubscriberUser> users;
+
+    // NOTE: use a tree map to maintain sorted order by user ID
+    private final Map<Integer, SubscriberUser> userMap =
+            new TreeMap<Integer, SubscriberUser>();
 
     /**
      * Constructs a model cache, initializing it with basic bundle.
      */
     CordModelCache() {
         currentBundle = new Bundle(BundleFactory.BASIC_BUNDLE);
-        users = new ArrayList<SubscriberUser>();
         initUsers();
     }
 
@@ -65,10 +69,10 @@
      * Used to initialize users for the demo. These are currently fake.
      */
     public void initUsers() {
-        users.add(createUser(1, "Mom's MacBook", MAC_1));
-        users.add(createUser(2, "Dad's iPad", MAC_2));
-        users.add(createUser(3, "Dick's laptop", MAC_3));
-        users.add(createUser(4, "Jane's laptop", MAC_4));
+        userMap.put(1, createUser(1, "Mom's MacBook", MAC_1));
+        userMap.put(2, createUser(2, "Dad's iPad", MAC_2));
+        userMap.put(3, createUser(3, "Dick's laptop", MAC_3));
+        userMap.put(4, createUser(4, "Jane's laptop", MAC_4));
     }
 
     private SubscriberUser createUser(int uid, String name, String mac) {
@@ -79,7 +83,6 @@
         return user;
     }
 
-
     /**
      * Returns the currently selected bundle.
      *
@@ -99,7 +102,7 @@
         BundleDescriptor bd = BundleFactory.bundleFromId(bundleId);
         currentBundle = new Bundle(bd);
         // update the user mementos
-        for (SubscriberUser user: users) {
+        for (SubscriberUser user: userMap.values()) {
             user.clearMementos();
             for (XosFunction f: currentBundle.functions()) {
                 user.setMemento(f.descriptor(), f.createMemento());
@@ -116,7 +119,7 @@
      * @return the list of users
      */
     public List<SubscriberUser> getUsers() {
-        return ImmutableList.copyOf(users);
+        return ImmutableList.copyOf(userMap.values());
     }
 
     /**
@@ -129,18 +132,25 @@
      */
     public void applyPerUserParam(String userId, String funcId,
                                   String param, String value) {
-        // FIXME: this is not right yet...
+
         int uid = Integer.parseInt(userId);
+        SubscriberUser user = userMap.get(uid);
+        checkNotNull(user, "unknown user id: " + uid);
+
         XosFunctionDescriptor xfd =
                 XosFunctionDescriptor.valueOf(funcId.toUpperCase());
-        XosFunctionFactory.apply(xfd, uid, param, value);
+
+        XosFunction func = currentBundle.findFunction(xfd);
+        checkNotNull(func, "function not part of bundle: " + funcId);
+
+        func.applyParam(user, param, value);
     }
 
     // =============
 
     private ArrayNode userJsonArray() {
         ArrayNode userList = arrayNode();
-        for (SubscriberUser user: users) {
+        for (SubscriberUser user: userMap.values()) {
             userList.add(UserFactory.toObjectNode(user));
         }
         return userList;
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/Bundle.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/Bundle.java
index 199c8d3..622d0e3 100644
--- a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/Bundle.java
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/Bundle.java
@@ -19,7 +19,8 @@
 
 import com.google.common.collect.ImmutableSet;
 
-import java.util.HashSet;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -27,7 +28,8 @@
  */
 public class Bundle {
     private final BundleDescriptor bundleDescriptor;
-    private final Set<XosFunction> functions;
+    private final Map<XosFunctionDescriptor, XosFunction> functionMap =
+        new HashMap<XosFunctionDescriptor, XosFunction>();
 
     /**
      * Constructs a new bundle instance.
@@ -36,7 +38,7 @@
      */
     public Bundle(BundleDescriptor bundleDescriptor) {
         this.bundleDescriptor = bundleDescriptor;
-        this.functions = initFunctions();
+        initFunctions();
     }
 
     /**
@@ -54,20 +56,16 @@
      * @return the functions
      */
     public Set<XosFunction> functions() {
-        return ImmutableSet.copyOf(functions);
+        return ImmutableSet.copyOf(functionMap.values());
     }
 
     /**
      * Creates an initial set of function instances.
-     *
-     * @return initial function instances
      */
-    private Set<XosFunction> initFunctions() {
-        Set<XosFunction> funcs = new HashSet<XosFunction>();
+    private void initFunctions() {
         for (XosFunctionDescriptor xfd: bundleDescriptor.functions()) {
-            funcs.add(createFunction(xfd));
+            functionMap.put(xfd, createFunction(xfd));
         }
-        return funcs;
     }
 
     private XosFunction createFunction(XosFunctionDescriptor xfd) {
@@ -83,4 +81,15 @@
         }
         return func;
     }
+
+    /**
+     * Returns the function instance for the specified descriptor, or returns
+     * null if function is not part of this bundle.
+     *
+     * @param xfd function descrriptor
+     * @return function instance
+     */
+    public XosFunction findFunction(XosFunctionDescriptor xfd) {
+        return functionMap.get(xfd);
+    }
 }
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/UrlFilterFunction.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/UrlFilterFunction.java
index a4787a1..d1f8483 100644
--- a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/UrlFilterFunction.java
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/UrlFilterFunction.java
@@ -19,6 +19,8 @@
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
 
+import static com.google.common.base.Preconditions.checkNotNull;
+
 /**
  * Specialization of XosFunction for URL filtering.
  */
@@ -41,6 +43,18 @@
     }
 
     @Override
+    public void applyParam(SubscriberUser user, String param, String value) {
+        Memento memo = user.getMemento(descriptor());
+        checkNotNull(memo, "missing memento for " + descriptor());
+        UrlFilterMemento ufMemo = (UrlFilterMemento) memo;
+
+        if (LEVEL.equals(param)) {
+            Level newLevel = Level.valueOf(value.toUpperCase());
+            ufMemo.setLevel(newLevel);
+        }
+    }
+
+    @Override
     public Memento createMemento() {
         return new UrlFilterMemento();
     }
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/XosFunctionFactory.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/XosFunctionFactory.java
index 93ed831..991b72b 100644
--- a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/XosFunctionFactory.java
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/XosFunctionFactory.java
@@ -58,20 +58,6 @@
     }
 
     /**
-     * Applies a parameter change for the given function, in the context of
-     * the specified user.
-     *
-     * @param xfd function context
-     * @param userId user identifier
-     * @param param parameter name
-     * @param value value to apply
-     */
-    public static void apply(XosFunctionDescriptor xfd, int userId,
-                             String param, String value) {
-        // TODO:
-    }
-
-    /**
      * Creates an object node representation of the profile for the
      * specified user.
      *
diff --git a/apps/demo/cord-gui/src/test/org/onosproject/cord/gui/CoreModelCacheTest.java b/apps/demo/cord-gui/src/test/org/onosproject/cord/gui/CoreModelCacheTest.java
index ede8641..373f2e4 100644
--- a/apps/demo/cord-gui/src/test/org/onosproject/cord/gui/CoreModelCacheTest.java
+++ b/apps/demo/cord-gui/src/test/org/onosproject/cord/gui/CoreModelCacheTest.java
@@ -91,8 +91,36 @@
         assertTrue("bad users family json", sameJson(USERS_FAMILY, json));
     }
 
+    @Test
+    public void setNewLevel() {
+        cache.setCurrentBundle("family");
+        JsonNode node = fromString(cache.jsonUsers());
+        assertEquals("wrong level", "PG", getMomsLevel(node));
+
+        cache.applyPerUserParam("1", "url_filter", "level", "R");
+
+        node = fromString(cache.jsonUsers());
+        assertEquals("wrong level", "R", getMomsLevel(node));
+    }
+
+    private String getMomsLevel(JsonNode node) {
+        JsonNode mom = node.get("users").elements().next();
+        assertEquals("wrong ID", 1, mom.get("id").asInt());
+        return mom.get("profile").get("url_filter").get("level").asText();
+    }
+
+
     // =============
 
+    private JsonNode fromString(String s) {
+        try {
+            return MAPPER.readTree(s);
+        } catch (IOException e) {
+            System.out.println("Exception: " + e);
+        }
+        return null;
+    }
+
     private boolean sameJson(String s1, String s2) {
         try {
             JsonNode tree1 = MAPPER.readTree(s1);