GUI -- Refactoring of server-side message handlers (Part One).

Change-Id: I895cef0545f7ba4b78a2adfa2bad9d889ca0104a
diff --git a/core/api/src/main/java/org/onosproject/ui/JsonUtils.java b/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
new file mode 100644
index 0000000..152fc9b
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
@@ -0,0 +1,107 @@
+/*
+ * 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.ui;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+/**
+ * Provides convenience methods for dealing with JSON nodes, arrays etc.
+ */
+public final class JsonUtils {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    // non-instantiable
+    private JsonUtils() { }
+
+    /**
+     * Wraps a message payload into an event structure for the given event
+     * type and sequence ID. Generally, the sequence ID should be a copy of
+     * the ID from the client request event.
+     *
+     * @param type    event type
+     * @param sid     sequence ID
+     * @param payload event payload
+     * @return the object node representation
+     */
+    public static ObjectNode envelope(String type, long sid, ObjectNode payload) {
+        ObjectNode event = MAPPER.createObjectNode();
+        event.put("event", type);
+        if (sid > 0) {
+            event.put("sid", sid);
+        }
+        event.set("payload", payload);
+        return event;
+    }
+
+    /**
+     * Returns the event type from the specified event.
+     * If the node does not have an "event" property, "unknown" is returned.
+     *
+     * @param event message event
+     * @return extracted event type
+     */
+    public static String eventType(ObjectNode event) {
+        return string(event, "event", "unknown");
+    }
+
+    /**
+     * Returns the payload from the specified event.
+     *
+     * @param event message event
+     * @return extracted payload object
+     */
+    public static ObjectNode payload(ObjectNode event) {
+        return (ObjectNode) event.path("payload");
+    }
+
+    /**
+     * Returns the specified node property as a number.
+     *
+     * @param node message event
+     * @param name property name
+     * @return property as number
+     */
+    public static long number(ObjectNode node, String name) {
+        return node.path(name).asLong();
+    }
+
+    /**
+     * Returns the specified node property as a string.
+     *
+     * @param node message event
+     * @param name property name
+     * @return property as a string
+     */
+    public static String string(ObjectNode node, String name) {
+        return node.path(name).asText();
+    }
+
+    /**
+     * Returns the specified node property as a string, with a default fallback.
+     *
+     * @param node         message event
+     * @param name         property name
+     * @param defaultValue fallback value if property is absent
+     * @return property as a string
+     */
+    public static String string(ObjectNode node, String name, String defaultValue) {
+        return node.path(name).asText(defaultValue);
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java b/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
index d9c1510..0482162 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
@@ -138,13 +138,17 @@
      * @return the object node representation
      */
     protected ObjectNode envelope(String type, long sid, ObjectNode payload) {
-        ObjectNode event = mapper.createObjectNode();
-        event.put("event", type);
-        if (sid > 0) {
-            event.put("sid", sid);
-        }
-        event.set("payload", payload);
-        return event;
+        return JsonUtils.envelope(type, sid, payload);
+    }
+
+    /**
+     * Returns the event type from the specified event.
+     *
+     * @param event the event
+     * @return the event type
+     */
+    protected String eventType(ObjectNode event) {
+        return JsonUtils.eventType(event);
     }
 
     /**
@@ -154,7 +158,7 @@
      * @return extracted payload object
      */
     protected ObjectNode payload(ObjectNode event) {
-        return (ObjectNode) event.path("payload");
+        return JsonUtils.payload(event);
     }
 
     /**
@@ -165,7 +169,7 @@
      * @return property as number
      */
     protected long number(ObjectNode node, String name) {
-        return node.path(name).asLong();
+        return JsonUtils.number(node, name);
     }
 
     /**
@@ -176,7 +180,7 @@
      * @return property as a string
      */
     protected String string(ObjectNode node, String name) {
-        return node.path(name).asText();
+        return JsonUtils.string(node, name);
     }
 
     /**
@@ -188,7 +192,21 @@
      * @return property as a string
      */
     protected String string(ObjectNode node, String name, String defaultValue) {
-        return node.path(name).asText(defaultValue);
+        return JsonUtils.string(node, name, defaultValue);
     }
 
+    /**
+     * Concatenates an arbitrary number of objects, using their
+     * toString() methods.
+     *
+     * @param items the items to concatenate
+     * @return a concatenated string
+     */
+    protected static String concat(Object... items) {
+        StringBuilder sb = new StringBuilder();
+        for (Object o : items) {
+            sb.append(o);
+        }
+        return sb.toString();
+    }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/AbstractTableRow.java b/core/api/src/main/java/org/onosproject/ui/table/AbstractTableRow.java
similarity index 73%
rename from web/gui/src/main/java/org/onosproject/ui/impl/AbstractTableRow.java
rename to core/api/src/main/java/org/onosproject/ui/table/AbstractTableRow.java
index 66db400..32a4396 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/AbstractTableRow.java
+++ b/core/api/src/main/java/org/onosproject/ui/table/AbstractTableRow.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.ui.impl;
+package org.onosproject.ui.table;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -30,18 +30,18 @@
 
     private static final ObjectMapper MAPPER = new ObjectMapper();
 
-    private final Map<String, String> data = new HashMap<>();
+    private final Map<String, String> cells = new HashMap<>();
 
     @Override
     public String get(String key) {
-        return data.get(key);
+        return cells.get(key);
     }
 
     @Override
     public ObjectNode toJsonNode() {
         ObjectNode result = MAPPER.createObjectNode();
         for (String id : columnIds()) {
-            result.put(id, data.get(id));
+            result.put(id, cells.get(id));
         }
         return result;
     }
@@ -54,12 +54,23 @@
     protected abstract String[] columnIds();
 
     /**
-     * Add a column ID to value binding.
+     * Add a column ID to cell value binding.
      *
      * @param id the column ID
      * @param value the cell value
      */
     protected void add(String id, String value) {
-        data.put(id, value);
+        cells.put(id, value);
+    }
+
+    /**
+     * Add a column ID to cell value binding.
+     * Note that value.toString() is invoked.
+     *
+     * @param id the column ID
+     * @param value the cell value
+     */
+    protected void add(String id, Object value) {
+        cells.put(id, value.toString());
     }
 }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/RowComparator.java b/core/api/src/main/java/org/onosproject/ui/table/RowComparator.java
similarity index 90%
rename from web/gui/src/main/java/org/onosproject/ui/impl/RowComparator.java
rename to core/api/src/main/java/org/onosproject/ui/table/RowComparator.java
index b588f48..9859a65 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/RowComparator.java
+++ b/core/api/src/main/java/org/onosproject/ui/table/RowComparator.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.ui.impl;
+package org.onosproject.ui.table;
 
 import java.util.Comparator;
 
@@ -22,7 +22,13 @@
  * Comparator for {@link TableRow}.
  */
 public class RowComparator implements Comparator<TableRow> {
-    public static enum Direction { ASC, DESC }
+    /** Designates the sort direction. */
+    public enum Direction {
+        /** Sort Ascending. */
+        ASC,
+        /** Sort Descending. */
+        DESC
+    }
 
     public static final String DESC_STR = "desc";
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TableRow.java b/core/api/src/main/java/org/onosproject/ui/table/TableRow.java
similarity index 78%
rename from web/gui/src/main/java/org/onosproject/ui/impl/TableRow.java
rename to core/api/src/main/java/org/onosproject/ui/table/TableRow.java
index 81a14ba..4775687 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TableRow.java
+++ b/core/api/src/main/java/org/onosproject/ui/table/TableRow.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package org.onosproject.ui.impl;
+package org.onosproject.ui.table;
 
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -23,6 +23,11 @@
  * Defines a table row abstraction to support sortable tables on the GUI.
  */
 public interface TableRow {
+
+    // TODO: Define TableCell interface and return that, rather than String
+    // The hope is that this will allow us to write a generic mechanism for
+    // selecting a comparator based on the cell type for the column, to be
+    // used for sorting the table rows.
     /**
      * Returns the value of the cell for the given column ID.
      *
diff --git a/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java b/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
new file mode 100644
index 0000000..9f63cba
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/table/TableUtils.java
@@ -0,0 +1,77 @@
+/*
+ * 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.ui.table;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.onosproject.ui.JsonUtils;
+
+/**
+ * Provides static utility methods for dealing with tables.
+ */
+public final class TableUtils {
+
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    // non-instantiable
+    private TableUtils() { }
+
+    /**
+     * Produces a JSON array node from the specified table rows.
+     *
+     * @param rows table rows
+     * @return JSON array
+     */
+    public static ArrayNode generateArrayNode(TableRow[] rows) {
+        ArrayNode array = MAPPER.createArrayNode();
+        for (TableRow r : rows) {
+            array.add(r.toJsonNode());
+        }
+        return array;
+    }
+
+    /**
+     * Creates a row comparator for the given request. The ID of the column
+     * to sort on is the payload's "sortCol" property (defaults to "id").
+     * The direction for the sort is the payload's "sortDir" property
+     * (defaults to "asc").
+     *
+     * @param payload the event payload
+     * @return a row comparator
+     */
+    public static RowComparator createRowComparator(ObjectNode payload) {
+        return createRowComparator(payload, "id");
+    }
+
+    /**
+     * Creates a row comparator for the given request. The ID of the column to
+     * sort on is the payload's "sortCol" property (or the specified default).
+     * The direction for the sort is the payload's "sortDir" property
+     * (defaults to "asc").
+     *
+     * @param payload the event payload
+     * @param defColId the default column ID
+     * @return a row comparator
+     */
+    public static RowComparator createRowComparator(ObjectNode payload,
+                                                    String defColId) {
+        String sortCol = JsonUtils.string(payload, "sortCol", defColId);
+        String sortDir = JsonUtils.string(payload, "sortDir", "asc");
+        return new RowComparator(sortCol, RowComparator.direction(sortDir));
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/AbstractTabularViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/AbstractTabularViewMessageHandler.java
deleted file mode 100644
index 494ad42..0000000
--- a/web/gui/src/main/java/org/onosproject/ui/impl/AbstractTabularViewMessageHandler.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.ui.impl;
-
-import com.fasterxml.jackson.databind.node.ArrayNode;
-import org.onosproject.ui.UiMessageHandler;
-
-import java.util.Set;
-
-/**
- * Base message handler for tabular views.
- */
-public abstract class AbstractTabularViewMessageHandler extends UiMessageHandler {
-
-    /**
-     * Creates a new tabular view message handler.
-     *
-     * @param messageTypes set of message types
-     */
-    protected AbstractTabularViewMessageHandler(Set<String> messageTypes) {
-        super(messageTypes);
-    }
-
-    /**
-     * Produces JSON from the specified array of rows.
-     *
-     * @param rows table rows
-     * @return JSON array
-     */
-    protected ArrayNode generateArrayNode(TableRow[] rows) {
-        ArrayNode array = mapper.createArrayNode();
-        for (TableRow r : rows) {
-            array.add(r.toJsonNode());
-        }
-        return array;
-    }
-
-    // TODO: possibly convert this into just a toolbox class
-    // TODO: extract and generalize other table constructs
-}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
index d19c987..08408dc 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
@@ -15,7 +15,6 @@
  */
 package org.onosproject.ui.impl;
 
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import org.onosproject.app.ApplicationAdminService;
@@ -23,6 +22,11 @@
 import org.onosproject.app.ApplicationState;
 import org.onosproject.core.Application;
 import org.onosproject.core.ApplicationId;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.AbstractTableRow;
+import org.onosproject.ui.table.RowComparator;
+import org.onosproject.ui.table.TableRow;
+import org.onosproject.ui.table.TableUtils;
 
 import java.util.Arrays;
 import java.util.List;
@@ -33,7 +37,7 @@
 /**
  * Message handler for application view related messages.
  */
-public class ApplicationViewMessageHandler extends AbstractTabularViewMessageHandler {
+public class ApplicationViewMessageHandler extends UiMessageHandler {
 
     /**
      * Creates a new message handler for the application messages.
@@ -44,7 +48,7 @@
 
     @Override
     public void process(ObjectNode message) {
-        String type = string(message, "event", "unknown");
+        String type = eventType(message);
         if (type.equals("appDataRequest")) {
             sendAppList(message);
         } else if (type.equals("appManagementRequest")) {
@@ -54,17 +58,13 @@
 
     private void sendAppList(ObjectNode message) {
         ObjectNode payload = payload(message);
-        String sortCol = string(payload, "sortCol", "id");
-        String sortDir = string(payload, "sortDir", "asc");
+        RowComparator rc = TableUtils.createRowComparator(payload);
 
         ApplicationService service = get(ApplicationService.class);
         TableRow[] rows = generateTableRows(service);
-        RowComparator rc =
-                new RowComparator(sortCol, RowComparator.direction(sortDir));
         Arrays.sort(rows, rc);
-        ArrayNode applications = generateArrayNode(rows);
         ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("apps", applications);
+        rootNode.set("apps", TableUtils.generateArrayNode(rows));
 
         connection().sendMessage("appDataResponse", 0, rootNode);
     }
@@ -95,7 +95,8 @@
     }
 
     /**
-     * TableRow implementation for {@link org.onosproject.core.Application applications}.
+     * TableRow implementation for
+     * {@link org.onosproject.core.Application applications}.
      */
     private static class ApplicationTableRow extends AbstractTableRow {
 
@@ -118,10 +119,10 @@
             ApplicationState state = service.getState(app.id());
             String iconId = state == ACTIVE ? ICON_ID_ACTIVE : ICON_ID_INACTIVE;
 
-            add(STATE, state.toString());
+            add(STATE, state);
             add(STATE_IID, iconId);
             add(ID, app.id().name());
-            add(VERSION, app.version().toString());
+            add(VERSION, app.version());
             add(ORIGIN, app.origin());
             add(DESC, app.description());
         }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
index ac3fb92..55592e3 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ClusterViewMessageHandler.java
@@ -16,7 +16,6 @@
 
 package org.onosproject.ui.impl;
 
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import org.joda.time.DateTime;
@@ -24,6 +23,11 @@
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.cluster.NodeId;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.AbstractTableRow;
+import org.onosproject.ui.table.RowComparator;
+import org.onosproject.ui.table.TableRow;
+import org.onosproject.ui.table.TableUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,7 +37,7 @@
 /**
  * Message handler for cluster view related messages.
  */
-public class ClusterViewMessageHandler extends AbstractTabularViewMessageHandler {
+public class ClusterViewMessageHandler extends UiMessageHandler {
 
     /**
      * Creates a new message handler for the cluster messages.
@@ -44,18 +48,21 @@
 
     @Override
     public void process(ObjectNode message) {
+        String type = eventType(message);
+        if (type.equals("clusterDataRequest")) {
+            sendClusterList(message);
+        }
+    }
+
+    private void sendClusterList(ObjectNode message) {
         ObjectNode payload = payload(message);
-        String sortCol = string(payload, "sortCol", "id");
-        String sortDir = string(payload, "sortDir", "asc");
+        RowComparator rc = TableUtils.createRowComparator(payload);
 
         ClusterService service = get(ClusterService.class);
         TableRow[] rows = generateTableRows(service);
-        RowComparator rc =
-                new RowComparator(sortCol, RowComparator.direction(sortDir));
         Arrays.sort(rows, rc);
-        ArrayNode clusterNodes = generateArrayNode(rows);
         ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("clusters", clusterNodes);
+        rootNode.set("clusters", TableUtils.generateArrayNode(rows));
 
         connection().sendMessage("clusterDataResponse", 0, rootNode);
     }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
index 9820f6d..5015cb5 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/DeviceViewMessageHandler.java
@@ -27,6 +27,11 @@
 import org.onosproject.net.Port;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.link.LinkService;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.AbstractTableRow;
+import org.onosproject.ui.table.RowComparator;
+import org.onosproject.ui.table.TableRow;
+import org.onosproject.ui.table.TableUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -37,7 +42,7 @@
 /**
  * Message handler for device view related messages.
  */
-public class DeviceViewMessageHandler extends AbstractTabularViewMessageHandler {
+public class DeviceViewMessageHandler extends UiMessageHandler {
 
     private static final String ID = "id";
     private static final String TYPE = "type";
@@ -68,36 +73,31 @@
     }
 
     @Override
-    public void process(ObjectNode event) {
-        String type = string(event, "event", "unknown");
+    public void process(ObjectNode message) {
+        String type = eventType(message);
         if (type.equals("deviceDataRequest")) {
-            dataRequest(event);
+            dataRequest(message);
         } else if (type.equals("deviceDetailsRequest")) {
-            detailsRequest(event);
+            detailsRequest(message);
         }
     }
 
-    private void dataRequest(ObjectNode event) {
-        ObjectNode payload = payload(event);
-        String sortCol = string(payload, "sortCol", "id");
-        String sortDir = string(payload, "sortDir", "asc");
+    private void dataRequest(ObjectNode message) {
+        ObjectNode payload = payload(message);
+        RowComparator rc = TableUtils.createRowComparator(payload);
 
         DeviceService service = get(DeviceService.class);
         MastershipService mastershipService = get(MastershipService.class);
-
         TableRow[] rows = generateTableRows(service, mastershipService);
-        RowComparator rc =
-                new RowComparator(sortCol, RowComparator.direction(sortDir));
         Arrays.sort(rows, rc);
-        ArrayNode devices = generateArrayNode(rows);
         ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("devices", devices);
+        rootNode.set("devices", TableUtils.generateArrayNode(rows));
 
         connection().sendMessage("deviceDataResponse", 0, rootNode);
     }
 
-    private void detailsRequest(ObjectNode event) {
-        ObjectNode payload = payload(event);
+    private void detailsRequest(ObjectNode message) {
+        ObjectNode payload = payload(message);
         String id = string(payload, "id", "of:0000000000000000");
 
         DeviceId deviceId = DeviceId.deviceId(id);
@@ -139,9 +139,7 @@
                                          MastershipService mastershipService) {
         List<TableRow> list = new ArrayList<>();
         for (Device dev : service.getDevices()) {
-            list.add(new DeviceTableRow(service,
-                                        mastershipService,
-                                        dev));
+            list.add(new DeviceTableRow(service, mastershipService, dev));
         }
         return list.toArray(new TableRow[list.size()]);
     }
@@ -159,13 +157,13 @@
 
         Set<Link> links = ls.getEgressLinks(new ConnectPoint(id, p.number()));
         if (!links.isEmpty()) {
-            String egressLinks = "";
+            StringBuilder egressLinks = new StringBuilder();
             for (Link l : links) {
                 ConnectPoint dest = l.dst();
-                egressLinks += dest.elementId().toString()
-                        + "/" + dest.port().toString() + " ";
+                egressLinks.append(dest.elementId()).append("/")
+                        .append(dest.port()).append(" ");
             }
-            port.put(LINK_DEST, egressLinks);
+            port.put(LINK_DEST, egressLinks.toString());
         }
 
         return port;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
index b14ba98..81965da 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/FlowViewMessageHandler.java
@@ -16,7 +16,6 @@
 
 package org.onosproject.ui.impl;
 
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import org.apache.commons.lang.WordUtils;
@@ -27,6 +26,11 @@
 import org.onosproject.net.flow.TrafficTreatment;
 import org.onosproject.net.flow.criteria.Criterion;
 import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.AbstractTableRow;
+import org.onosproject.ui.table.RowComparator;
+import org.onosproject.ui.table.TableRow;
+import org.onosproject.ui.table.TableUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -37,7 +41,7 @@
 /**
  * Message handler for flow view related messages.
  */
-public class FlowViewMessageHandler extends AbstractTabularViewMessageHandler {
+public class FlowViewMessageHandler extends UiMessageHandler {
 
     private static final String NO_DEV = "none";
 
@@ -50,10 +54,16 @@
 
     @Override
     public void process(ObjectNode message) {
+        String type = eventType(message);
+        if (type.equals("flowDataRequest")) {
+            sendFlowList(message);
+        }
+    }
+
+    private void sendFlowList(ObjectNode message) {
         ObjectNode payload = payload(message);
+        RowComparator rc = TableUtils.createRowComparator(payload);
         String uri = string(payload, "devId", NO_DEV);
-        String sortCol = string(payload, "sortCol", "id");
-        String sortDir = string(payload, "sortDir", "asc");
 
         ObjectNode rootNode;
         if (uri.equals(NO_DEV)) {
@@ -61,16 +71,11 @@
             rootNode.set("flows", mapper.createArrayNode());
         } else {
             DeviceId deviceId = DeviceId.deviceId(uri);
-
             FlowRuleService service = get(FlowRuleService.class);
             TableRow[] rows = generateTableRows(service, deviceId);
-            RowComparator rc =
-                    new RowComparator(sortCol, RowComparator.direction(sortDir));
             Arrays.sort(rows, rc);
-            ArrayNode flows = generateArrayNode(rows);
-
             rootNode = mapper.createObjectNode();
-            rootNode.set("flows", flows);
+            rootNode.set("flows", TableUtils.generateArrayNode(rows));
         }
 
         connection().sendMessage("flowDataResponse", 0, rootNode);
@@ -191,7 +196,6 @@
             return sb;
         }
 
-
         @Override
         protected String[] columnIds() {
             return COL_IDS;
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
index 689a266..472ae16 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/HostViewMessageHandler.java
@@ -15,13 +15,17 @@
  */
 package org.onosproject.ui.impl;
 
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.Host;
 import org.onosproject.net.HostLocation;
 import org.onosproject.net.host.HostService;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.AbstractTableRow;
+import org.onosproject.ui.table.RowComparator;
+import org.onosproject.ui.table.TableRow;
+import org.onosproject.ui.table.TableUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -32,7 +36,7 @@
 /**
  * Message handler for host view related messages.
  */
-public class HostViewMessageHandler extends AbstractTabularViewMessageHandler {
+public class HostViewMessageHandler extends UiMessageHandler {
 
     /**
      * Creates a new message handler for the host messages.
@@ -43,18 +47,21 @@
 
     @Override
     public void process(ObjectNode message) {
+        String type = eventType(message);
+        if (type.equals("hostDataRequest")) {
+            sendHostList(message);
+        }
+    }
+
+    private void sendHostList(ObjectNode message) {
         ObjectNode payload = payload(message);
-        String sortCol = string(payload, "sortCol", "id");
-        String sortDir = string(payload, "sortDir", "asc");
+        RowComparator rc = TableUtils.createRowComparator(payload);
 
         HostService service = get(HostService.class);
         TableRow[] rows = generateTableRows(service);
-        RowComparator rc =
-                new RowComparator(sortCol, RowComparator.direction(sortDir));
         Arrays.sort(rows, rc);
-        ArrayNode hosts = generateArrayNode(rows);
         ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("hosts", hosts);
+        rootNode.set("hosts", TableUtils.generateArrayNode(rows));
 
         connection().sendMessage("hostDataResponse", 0, rootNode);
     }
@@ -89,12 +96,11 @@
             HostLocation location = h.location();
 
             add(TYPE_IID, getTypeIconId(h));
-            add(ID, h.id().toString());
-            add(MAC, h.mac().toString());
-            add(VLAN, h.vlan().toString());
-            add(IPS, h.ipAddresses().toString());
-            add(LOCATION, (location.deviceId().toString() + '/' +
-                           location.port().toString()));
+            add(ID, h.id());
+            add(MAC, h.mac());
+            add(VLAN, h.vlan());
+            add(IPS, h.ipAddresses());
+            add(LOCATION, concat(location.deviceId(), "/", location.port()));
         }
 
         private String getTypeIconId(Host host) {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
index 8277465..4924d4b 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/IntentViewMessageHandler.java
@@ -15,7 +15,6 @@
  */
 package org.onosproject.ui.impl;
 
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import org.onosproject.core.ApplicationId;
@@ -32,6 +31,11 @@
 import org.onosproject.net.intent.PathIntent;
 import org.onosproject.net.intent.PointToPointIntent;
 import org.onosproject.net.intent.SinglePointToMultiPointIntent;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.AbstractTableRow;
+import org.onosproject.ui.table.RowComparator;
+import org.onosproject.ui.table.TableRow;
+import org.onosproject.ui.table.TableUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -41,7 +45,7 @@
 /**
  * Message handler for intent view related messages.
  */
-public class IntentViewMessageHandler extends AbstractTabularViewMessageHandler {
+public class IntentViewMessageHandler extends UiMessageHandler {
 
     /**
      * Creates a new message handler for the intent messages.
@@ -52,18 +56,21 @@
 
     @Override
     public void process(ObjectNode message) {
+        String type = eventType(message);
+        if (type.equals("intentDataRequest")) {
+            sendIntentList(message);
+        }
+    }
+
+    private void sendIntentList(ObjectNode message) {
         ObjectNode payload = payload(message);
-        String sortCol = string(payload, "sortCol", "appId");
-        String sortDir = string(payload, "sortDir", "asc");
+        RowComparator rc = TableUtils.createRowComparator(payload);
 
         IntentService service = get(IntentService.class);
         TableRow[] rows = generateTableRows(service);
-        RowComparator rc =
-                new RowComparator(sortCol, RowComparator.direction(sortDir));
         Arrays.sort(rows, rc);
-        ArrayNode intents = generateArrayNode(rows);
         ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("intents", intents);
+        rootNode.set("intents", TableUtils.generateArrayNode(rows));
 
         connection().sendMessage("intentDataResponse", 0, rootNode);
     }
@@ -222,10 +229,10 @@
         public IntentTableRow(Intent intent) {
             ApplicationId appid = intent.appId();
 
-            add(APP_ID, String.valueOf(appid.id()) + " : " + appid.name());
-            add(KEY, intent.key().toString());
+            add(APP_ID, concat(appid.id(), " : ", appid.name()));
+            add(KEY, intent.key());
             add(TYPE, intent.getClass().getSimpleName());
-            add(PRIORITY, Integer.toString(intent.priority()));
+            add(PRIORITY, intent.priority());
             add(RESOURCES, formatResources(intent));
             add(DETAILS, formatDetails(intent));
         }
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
index 7d496f5..6683188 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/LinkViewMessageHandler.java
@@ -16,7 +16,6 @@
 
 package org.onosproject.ui.impl;
 
-import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
@@ -24,7 +23,12 @@
 import org.onosproject.net.Link;
 import org.onosproject.net.LinkKey;
 import org.onosproject.net.link.LinkService;
+import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.impl.TopologyViewMessageHandlerBase.BiLink;
+import org.onosproject.ui.table.AbstractTableRow;
+import org.onosproject.ui.table.RowComparator;
+import org.onosproject.ui.table.TableRow;
+import org.onosproject.ui.table.TableUtils;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -36,7 +40,7 @@
 /**
  * Message handler for link view related messages.
  */
-public class LinkViewMessageHandler extends AbstractTabularViewMessageHandler {
+public class LinkViewMessageHandler extends UiMessageHandler {
 
     /**
      * Creates a new message handler for the link messages.
@@ -47,18 +51,21 @@
 
     @Override
     public void process(ObjectNode message) {
+        String type = eventType(message);
+        if (type.equals("linkDataRequest")) {
+            sendLinkList(message);
+        }
+    }
+
+    private void sendLinkList(ObjectNode message) {
         ObjectNode payload = payload(message);
-        String sortCol = string(payload, "sortCol", "one");
-        String sortDir = string(payload, "sortDir", "asc");
+        RowComparator rc = TableUtils.createRowComparator(payload, "one");
 
         LinkService service = get(LinkService.class);
         TableRow[] rows = generateTableRows(service);
-        RowComparator rc =
-                new RowComparator(sortCol, RowComparator.direction(sortDir));
         Arrays.sort(rows, rc);
-        ArrayNode links = generateArrayNode(rows);
         ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("links", links);
+        rootNode.set("links", TableUtils.generateArrayNode(rows));
 
         connection().sendMessage("linkDataResponse", 0, rootNode);
     }
@@ -99,8 +106,8 @@
             ConnectPoint dst = link.one.dst();
             linkState(link);
 
-            add(ONE, src.elementId().toString() + "/" + src.port().toString());
-            add(TWO, dst.elementId().toString() + "/" + dst.port().toString());
+            add(ONE, concat(src.elementId(), "/", src.port()));
+            add(TWO, concat(dst.elementId(), "/", dst.port()));
             add(TYPE, linkType(link).toLowerCase());
             add(STATE, linkState(link));
             add(DIRECTION, link.two != null ? "A <--> B" : "A --> B");
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
index 9beeb94..8ba8d3a 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandler.java
@@ -131,7 +131,8 @@
      * Creates a new web-socket for serving data to GUI topology view.
      */
     public TopologyViewMessageHandler() {
-        super(ImmutableSet.of("topoStart", "topoStop",
+        super(ImmutableSet.of("topoStart",
+                              "topoStop",
                               "requestDetails",
                               "updateMeta",
                               "addHostIntent",