CORD Subscriber GUI -- More bundle wrangling.

Change-Id: I2fafdb281712d7747399d61611c3d4bb663a39b5
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 847527f..f09ecfe 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
@@ -17,9 +17,75 @@
 
 package org.onosproject.cord.gui;
 
+import com.google.common.collect.ImmutableList;
+import org.onosproject.cord.gui.model.Bundle;
+import org.onosproject.cord.gui.model.BundleDescriptor;
+import org.onosproject.cord.gui.model.BundleFactory;
+import org.onosproject.cord.gui.model.SubscriberUser;
+
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * In memory cache of the model of the subscriber's account.
  */
 public class CordModelCache {
 
+    // faked for the demo
+    private static final int SUBSCRIBER_ID = 92;
+    private static final String MAC_1 = "010203040506";
+    private static final String MAC_2 = "010203040507";
+    private static final String MAC_3 = "010203040508";
+    private static final String MAC_4 = "010203040509";
+
+    private Bundle currentBundle;
+    private final List<SubscriberUser> users;
+
+    /**
+     * Constructs a model cache, initializing it with basic bundle.
+     */
+    public CordModelCache() {
+        currentBundle = new Bundle(BundleFactory.BASIC_BUNDLE);
+        users = new ArrayList<SubscriberUser>();
+        initUsers();
+    }
+
+    /**
+     * Used to initialize users for the demo. These are currently fake.
+     */
+    public void initUsers() {
+        users.add(new SubscriberUser(1, "Mom's MacBook", MAC_1));
+        users.add(new SubscriberUser(2, "Dad's iPad", MAC_2));
+        users.add(new SubscriberUser(3, "Dick's laptop", MAC_3));
+        users.add(new SubscriberUser(4, "Jane's laptop", MAC_4));
+    }
+
+    /**
+     * Returns the currently selected bundle.
+     *
+     * @return current bundle
+     */
+    public Bundle getCurrentBundle() {
+        return currentBundle;
+    }
+
+    /**
+     * Sets a new bundle.
+     *
+     * @param bundleId bundle identifier
+     * @throws IllegalArgumentException if bundle ID is unknown
+     */
+    public void setCurrentBundle(String bundleId) {
+        BundleDescriptor bdesc = BundleFactory.bundleFromId(bundleId);
+        currentBundle = new Bundle(bdesc);
+    }
+
+    /**
+     * Returns the list of current users for this subscriber account.
+     *
+     * @return the list of users
+     */
+    public List<SubscriberUser> getUsers() {
+        return ImmutableList.copyOf(users);
+    }
 }
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
new file mode 100644
index 0000000..02181bc
--- /dev/null
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/Bundle.java
@@ -0,0 +1,44 @@
+/*
+ * 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.cord.gui.model;
+
+/**
+ * Encapsulates a bundle, including current state.
+ */
+public class Bundle {
+    private final BundleDescriptor bundleDescriptor;
+
+    /**
+     * Constructs a new bundle instance.
+     *
+     * @param bundleDescriptor the descriptor
+     */
+    public Bundle(BundleDescriptor bundleDescriptor) {
+        this.bundleDescriptor = bundleDescriptor;
+    }
+
+    /**
+     * Returns the bundle descriptor.
+     *
+     * @return the descriptor
+     */
+    public BundleDescriptor descriptor() {
+        return bundleDescriptor;
+    }
+
+}
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/BundleFactory.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/BundleFactory.java
index 7f0bad2..0b3c90e 100644
--- a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/BundleFactory.java
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/BundleFactory.java
@@ -17,14 +17,20 @@
 
 package org.onosproject.cord.gui.model;
 
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableList;
 
 import java.util.List;
 
 /**
- * Utility factory for creating bundles and functions etc.
+ * Utility factory for creating and/or operating on bundles.
  */
-public class BundleFactory {
+public class BundleFactory extends JsonFactory {
+
+    private static final String BUNDLE = "bundle";
+    private static final String BUNDLES = "bundles";
+    private static final String FUNCTIONS = "functions";
 
     private static final String BASIC_ID = "basic";
     private static final String BASIC_DISPLAY_NAME = "Basic Bundle";
@@ -35,23 +41,83 @@
     // no instantiation
     private BundleFactory() {}
 
-    private static final BundleDescriptor BASIC =
+    /**
+     * Designates the BASIC bundle.
+     */
+    public static final BundleDescriptor BASIC_BUNDLE =
             new DefaultBundleDescriptor(BASIC_ID, BASIC_DISPLAY_NAME,
                                         XosFunctionDescriptor.INTERNET,
                                         XosFunctionDescriptor.FIREWALL);
 
-    private static final BundleDescriptor FAMILY =
+    /**
+     * Designates the FAMILY bundle.
+     */
+    public static final BundleDescriptor FAMILY_BUNDLE =
             new DefaultBundleDescriptor(FAMILY_ID, FAMILY_DISPLAY_NAME,
                                         XosFunctionDescriptor.INTERNET,
                                         XosFunctionDescriptor.FIREWALL,
                                         XosFunctionDescriptor.URL_FILTER);
 
+    // all bundles, in the order they should be listed in the GUI
+    private static final List<BundleDescriptor> ALL_BUNDLES = ImmutableList.of(
+            BASIC_BUNDLE,
+            FAMILY_BUNDLE
+    );
+
     /**
      * Returns the list of available bundles.
      *
      * @return available bundles
      */
     public static List<BundleDescriptor> availableBundles() {
-        return ImmutableList.of(BASIC, FAMILY);
+        return ALL_BUNDLES;
+    }
+
+    /**
+     * Returns the bundle descriptor for the given identifier.
+     *
+     * @param bundleId bundle identifier
+     * @return bundle descriptor
+     * @throws IllegalArgumentException if bundle ID is unknown
+     */
+    public static BundleDescriptor bundleFromId(String bundleId) {
+        for (BundleDescriptor bd : ALL_BUNDLES) {
+            if (bd.id().equals(bundleId)) {
+                return bd;
+            }
+        }
+        throw new IllegalArgumentException("unknown bundle: " + bundleId);
+    }
+
+    /**
+     * Returns a JSON string representation of the given bundle.
+     *
+     * @param bundle the bundle
+     * @return JSON string
+     */
+    public static String toJson(Bundle bundle) {
+        ObjectNode root = objectNode();
+
+        ObjectNode bnode = objectNode()
+                .put(ID, bundle.descriptor().id())
+                .put(NAME, bundle.descriptor().displayName());
+
+        ArrayNode funcs = arrayNode();
+        for (XosFunctionDescriptor xfd: bundle.descriptor().functions()) {
+            funcs.add(XosFunctionFactory.toObjectNode(xfd));
+        }
+        bnode.set(FUNCTIONS, funcs);
+        root.set(BUNDLE, bnode);
+
+        ArrayNode bundles = arrayNode();
+        for (BundleDescriptor bd: BundleFactory.availableBundles()) {
+            ObjectNode bdnode = objectNode()
+                    .put(ID, bd.id())
+                    .put(NAME, bd.displayName());
+            bundles.add(bdnode);
+        }
+        root.set(BUNDLES, bundles);
+        return root.toString();
+
     }
 }
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/DefaultBundleDescriptor.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/DefaultBundleDescriptor.java
index 45a2bda..509e179 100644
--- a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/DefaultBundleDescriptor.java
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/DefaultBundleDescriptor.java
@@ -57,4 +57,27 @@
     public Set<XosFunctionDescriptor> functions() {
         return functions;
     }
+
+    @Override
+    public String toString() {
+        return "{BundleDescriptor: " + displayName + "}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        DefaultBundleDescriptor that = (DefaultBundleDescriptor) o;
+        return id.equals(that.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
 }
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/JsonFactory.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/JsonFactory.java
new file mode 100644
index 0000000..5592b51
--- /dev/null
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/JsonFactory.java
@@ -0,0 +1,51 @@
+/*
+ * 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.cord.gui.model;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Base class for factories that convert objects to JSON.
+ */
+public abstract class JsonFactory {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    protected static final String ID = "id";
+    protected static final String NAME = "name";
+
+    /**
+     * Returns a freshly minted object node.
+     *
+     * @return empty object node
+     */
+    protected static ObjectNode objectNode() {
+        return mapper.createObjectNode();
+    }
+
+    /**
+     * Returns a freshly minted array node.
+     *
+     * @return empty array node
+     */
+    protected static ArrayNode arrayNode() {
+        return mapper.createArrayNode();
+    }
+}
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/SubscriberUser.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/SubscriberUser.java
new file mode 100644
index 0000000..151df57
--- /dev/null
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/SubscriberUser.java
@@ -0,0 +1,67 @@
+/*
+ * 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.cord.gui.model;
+
+/**
+ * Designates a user of a subscriber's account.
+ */
+public class SubscriberUser {
+    private final int id;
+    private final String name;
+    private final String mac;
+
+    /**
+     * Constructs a subscriber user from the given parameters.
+     *
+     * @param id internal identifier
+     * @param name display name
+     * @param mac MAC address of the associated device
+     */
+    public SubscriberUser(int id, String name, String mac) {
+        this.id = id;
+        this.name = name;
+        this.mac = mac;
+    }
+
+    /**
+     * Returns the internal identifier.
+     *
+     * @return the identifier
+     */
+    public int id() {
+        return id;
+    }
+
+    /**
+     * Returns the display name.
+     *
+     * @return display name
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Returns the MAC address of the associated device.
+     *
+     * @return MAC address
+     */
+    public String mac() {
+        return mac;
+    }
+}
diff --git a/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/UserFactory.java b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/UserFactory.java
new file mode 100644
index 0000000..e143366
--- /dev/null
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/UserFactory.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cord.gui.model;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Utility functions on users.
+ */
+public class UserFactory extends JsonFactory {
+
+    private static final String MAC = "mac";
+    private static final String PROFILE = "profile";
+
+    // no instantiation
+    private UserFactory() {}
+
+    /**
+     * Returns an object node representation of the given user.
+     *
+     * @param user the user
+     * @return object node
+     */
+    public static ObjectNode toObjectNode(SubscriberUser user) {
+        ObjectNode root = objectNode()
+                .put(ID, user.id())
+                .put(NAME, user.name())
+                .put(MAC, user.mac());
+        // TODO: add profile data
+        return root;
+    }
+
+}
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
new file mode 100644
index 0000000..ea9ec35
--- /dev/null
+++ b/apps/demo/cord-gui/src/main/java/org/onosproject/cord/gui/model/XosFunctionFactory.java
@@ -0,0 +1,86 @@
+package org.onosproject.cord.gui.model;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility factory for operating on XOS functions.
+ */
+public class XosFunctionFactory extends JsonFactory {
+
+    private static final String DESC = "desc";
+    private static final String PARAMS = "params";
+
+    private static final String LEVEL = "level";
+    private static final String LEVELS = "levels";
+
+
+    // URL Filtering Levels...
+    private static final String PG = "PG";
+    private static final String PG13 = "PG-13";
+    private static final String R = "R";
+
+    private static final String[] FILTER_LEVELS = { PG, PG13, R };
+    private static final String DEFAULT_FILTER_LEVEL = PG;
+
+
+    // no instantiation
+    private XosFunctionFactory() {}
+
+    /**
+     * Produces the JSON representation of the given XOS function descriptor.
+     *
+     * @param xfd function descriptor
+     * @return JSON encoding
+     */
+    public static ObjectNode toObjectNode(XosFunctionDescriptor xfd) {
+        ObjectNode root = objectNode()
+                .put(ID, xfd.id())
+                .put(NAME, xfd.displayName())
+                .put(DESC, xfd.description());
+        root.set(PARAMS, paramsForXfd(xfd));
+        return root;
+    }
+
+    private static JsonNode paramsForXfd(XosFunctionDescriptor xfd) {
+        ParamStructFactory psf = PARAM_MAP.get(xfd);
+        if (psf == null) {
+            psf = DEF_PARAMS;
+        }
+        return psf.params();
+    }
+
+    // ==== handling different parameter structures...
+    private static final Map<XosFunctionDescriptor, ParamStructFactory>
+        PARAM_MAP = new HashMap<XosFunctionDescriptor, ParamStructFactory>();
+
+    private static final ParamStructFactory DEF_PARAMS = new ParamStructFactory();
+    static {
+        PARAM_MAP.put(XosFunctionDescriptor.URL_FILTER, new UrlFilterParams());
+    }
+
+    // private parameter structure creator
+    static class ParamStructFactory {
+        ObjectNode params() {
+            return objectNode();
+        }
+    }
+
+    static class UrlFilterParams extends ParamStructFactory {
+        @Override
+        ObjectNode params() {
+            ObjectNode result = objectNode();
+            result.put(LEVEL, DEFAULT_FILTER_LEVEL);
+            ArrayNode levels = arrayNode();
+            for (String lvl: FILTER_LEVELS) {
+                levels.add(lvl);
+            }
+            result.set(LEVELS, levels);
+            return result;
+        }
+    }
+}