GUI -- Huge Refactoring of server-side message handlers (Part Two).
--- Well, it compiles, and seems to work, with the cursory testing I've done...

Change-Id: I0e59657c134e109850e4770766083370dfd9fdc2
diff --git a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java
index 1f31672..b3280c8 100644
--- a/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java
+++ b/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java
@@ -27,10 +27,11 @@
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.osgi.ServiceDirectory;
 import org.onosproject.intentperf.IntentPerfCollector.Sample;
+import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiExtension;
 import org.onosproject.ui.UiExtensionService;
-import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.onosproject.ui.UiView;
 
 import java.util.Collection;
@@ -48,14 +49,20 @@
 @Service(value = IntentPerfUi.class)
 public class IntentPerfUi {
 
+    private static final String INTENT_PERF_START = "intentPerfStart";
+    private static final String INTENT_PERF_STOP = "intentPerfStop";
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected UiExtensionService uiExtensionService;
 
     private final Set<StreamingControl> handlers = synchronizedSet(new HashSet<>());
 
-    private List<UiView> views = ImmutableList.of(new UiView(OTHER, "intentPerf", "Intent Performance"));
-    private UiExtension uiExtension = new UiExtension(views, this::newHandlers,
-                                                      getClass().getClassLoader());
+    private List<UiView> views = ImmutableList.of(
+            new UiView(OTHER, "intentPerf", "Intent Performance")
+    );
+
+    private UiExtension uiExtension =
+            new UiExtension(views, this::newHandlers, getClass().getClassLoader());
 
     private IntentPerfCollector collector;
 
@@ -90,25 +97,22 @@
     }
 
     // Creates and returns session specific message handler.
-    private Collection<UiMessageHandler> newHandlers() {
+    private Collection<UiMessageHandlerTwo> newHandlers() {
         return ImmutableList.of(new StreamingControl());
     }
 
+
     // UI Message handlers for turning on/off reporting to a session.
-    private class StreamingControl extends UiMessageHandler {
+    private class StreamingControl extends UiMessageHandlerTwo {
 
         private boolean streamingEnabled = false;
 
-        protected StreamingControl() {
-            super(ImmutableSet.of("intentPerfStart", "intentPerfStop"));
-        }
-
         @Override
-        public void process(ObjectNode message) {
-            streamingEnabled = message.path("event").asText("unknown").equals("intentPerfStart");
-            if (streamingEnabled) {
-                sendInitData();
-            }
+        protected Collection<RequestHandler> getHandlers() {
+            return ImmutableSet.of(
+                    new IntentPerfStart(),
+                    new IntentPerfStop()
+            );
         }
 
         @Override
@@ -129,17 +133,6 @@
             }
         }
 
-        private void sendInitData() {
-            ObjectNode rootNode = mapper.createObjectNode();
-            ArrayNode an = mapper.createArrayNode();
-            ArrayNode sn = mapper.createArrayNode();
-            rootNode.set("headers", an);
-            rootNode.set("samples", sn);
-
-            collector.getSampleHeaders().forEach(an::add);
-            collector.getSamples().forEach(s -> sn.add(sampleNode(s)));
-            connection().sendMessage("intentPerfInit", 0, rootNode);
-        }
 
         private ObjectNode sampleNode(Sample sample) {
             ObjectNode sampleNode = mapper.createObjectNode();
@@ -153,6 +146,47 @@
             return sampleNode;
         }
 
+        // ======================================================================
+
+        private final class IntentPerfStart extends RequestHandler {
+
+            private IntentPerfStart() {
+                super(INTENT_PERF_START);
+            }
+
+            @Override
+            public void process(long sid, ObjectNode payload) {
+                streamingEnabled = true;
+                sendInitData();
+            }
+
+            private void sendInitData() {
+                ObjectNode rootNode = MAPPER.createObjectNode();
+                ArrayNode an = MAPPER.createArrayNode();
+                ArrayNode sn = MAPPER.createArrayNode();
+                rootNode.set("headers", an);
+                rootNode.set("samples", sn);
+
+                collector.getSampleHeaders().forEach(an::add);
+                collector.getSamples().forEach(s -> sn.add(sampleNode(s)));
+                sendMessage("intentPerfInit", 0, rootNode);
+            }
+        }
+
+        // ======================================================================
+
+        private final class IntentPerfStop extends RequestHandler {
+
+            private IntentPerfStop() {
+                super(INTENT_PERF_STOP);
+            }
+
+            @Override
+            public void process(long sid, ObjectNode payload) {
+                streamingEnabled = false;
+            }
+        }
+
     }
 
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/JsonUtils.java b/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
index 152fc9b..db753e2 100644
--- a/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
+++ b/core/api/src/main/java/org/onosproject/ui/JsonUtils.java
@@ -61,6 +61,17 @@
     }
 
     /**
+     * Returns the sequence identifier from the specified event, or 0 (zero)
+     * if the "sid" property does not exist.
+     *
+     * @param event message event
+     * @return extracted sequence identifier
+     */
+    public static long sid(ObjectNode event) {
+        return number(event, "sid");
+    }
+
+    /**
      * Returns the payload from the specified event.
      *
      * @param event message event
@@ -95,7 +106,7 @@
     /**
      * Returns the specified node property as a string, with a default fallback.
      *
-     * @param node         message event
+     * @param node         object node
      * @param name         property name
      * @param defaultValue fallback value if property is absent
      * @return property as a string
@@ -104,4 +115,15 @@
         return node.path(name).asText(defaultValue);
     }
 
+    /**
+     * Returns the specified node property as an object node.
+     *
+     * @param node object node
+     * @param name property name
+     * @return property as a node
+     */
+    public static ObjectNode node(ObjectNode node, String name) {
+        return (ObjectNode) node.path(name);
+    }
+
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/RequestHandler.java b/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
new file mode 100644
index 0000000..7231dcf
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/RequestHandler.java
@@ -0,0 +1,125 @@
+/*
+ * 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;
+
+/**
+ * Abstraction of an entity that handles a specific request from the
+ * user interface client.
+ *
+ * @see UiMessageHandlerTwo
+ */
+public abstract class RequestHandler {
+
+    protected static final ObjectMapper MAPPER = new ObjectMapper();
+
+    private final String eventType;
+    private UiMessageHandlerTwo parent;
+
+
+    public RequestHandler(String eventType) {
+        this.eventType = eventType;
+    }
+
+    // package private
+    void setParent(UiMessageHandlerTwo parent) {
+        this.parent = parent;
+    }
+
+    /**
+     * Returns the event type that this handler handles.
+     *
+     * @return event type
+     */
+    public String eventType() {
+        return eventType;
+    }
+
+    /**
+     * Processes the incoming message payload from the client.
+     *
+     * @param sid message sequence identifier
+     * @param payload request message payload
+     */
+    public abstract void process(long sid, ObjectNode payload);
+
+
+
+    // ===================================================================
+    // === Convenience methods...
+
+    /**
+     * Returns implementation of the specified service class.
+     *
+     * @param serviceClass service class
+     * @param <T>          type of service
+     * @return implementation class
+     * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
+     */
+    protected <T> T get(Class<T> serviceClass) {
+        return parent.directory().get(serviceClass);
+    }
+
+    /**
+     * Sends a message back to the client.
+     *
+     * @param eventType message event type
+     * @param sid       message sequence identifier
+     * @param payload   message payload
+     */
+    protected void sendMessage(String eventType, long sid, ObjectNode payload) {
+        parent.connection().sendMessage(eventType, sid, payload);
+    }
+
+    /**
+     * Sends a message back to the client.
+     * Here, the message is preformatted; the assumption is it has its
+     * eventType, sid and payload attributes already filled in.
+     *
+     * @param message the message to send
+     */
+    protected void sendMessage(ObjectNode message) {
+        parent.connection().sendMessage(message);
+    }
+
+    /**
+     * Allows one request handler to pass the event on to another for
+     * further processing.
+     * Note that the message handlers must be defined in the same parent.
+     *
+     * @param eventType event type
+     * @param sid       sequence identifier
+     * @param payload   message payload
+     */
+    protected void chain(String eventType, long sid, ObjectNode payload) {
+        parent.exec(eventType, sid, payload);
+    }
+
+    // ===================================================================
+
+
+    // FIXME : Javadocs
+    protected String string(ObjectNode node, String key) {
+        return JsonUtils.string(node, key);
+    }
+
+    protected String string(ObjectNode node, String key, String defValue) {
+        return JsonUtils.string(node, key, defValue);
+    }
+
+}
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 0482162..00b3595 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
@@ -40,6 +40,7 @@
  * }
  * </pre>
  */
+@Deprecated
 public abstract class UiMessageHandler {
 
     private final Set<String> messageTypes;
diff --git a/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java b/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java
index 522daa8..23bd5d4 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerFactory.java
@@ -28,6 +28,6 @@
      *
      * @return collection of new handlers
      */
-    Collection<UiMessageHandler> newHandlers();
+    Collection<UiMessageHandlerTwo> newHandlers();
 
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerTwo.java b/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerTwo.java
new file mode 100644
index 0000000..915bcaf
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiMessageHandlerTwo.java
@@ -0,0 +1,169 @@
+/*
+ * 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;
+import org.onlab.osgi.ServiceDirectory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * Abstraction of an entity capable of processing a JSON message from the user
+ * interface client.
+ * <p>
+ * The message is a JSON object with the following structure:
+ * </p>
+ * <pre>
+ * {
+ *     "type": "<em>event-type</em>",
+ *     "sid": "<em>sequence-number</em>",
+ *     "payload": {
+ *         <em>arbitrary JSON object structure</em>
+ *     }
+ * }
+ * </pre>
+ */
+public abstract class UiMessageHandlerTwo {
+
+    private final Map<String, RequestHandler> handlerMap = new HashMap<>();
+
+    private UiConnection connection;
+    private ServiceDirectory directory;
+
+    /**
+     * Mapper for creating ObjectNodes and ArrayNodes etc.
+     */
+    protected final ObjectMapper mapper = new ObjectMapper();
+
+    /**
+     * Binds the handlers returned from {@link #getHandlers()} to this
+     * instance.
+     */
+    void bindHandlers() {
+        Collection<RequestHandler> handlers = getHandlers();
+        checkNotNull(handlers, "Handlers cannot be null");
+        checkArgument(!handlers.isEmpty(), "Handlers cannot be empty");
+
+        for (RequestHandler h : handlers) {
+            h.setParent(this);
+            handlerMap.put(h.eventType(), h);
+        }
+    }
+
+    /**
+     * Subclasses must return the collection of handlers for the
+     * message types they handle.
+     *
+     * @return the message handler instances
+     */
+    protected abstract Collection<RequestHandler> getHandlers();
+
+    /**
+     * Returns the set of message types which this handler is capable of
+     * processing.
+     *
+     * @return set of message types
+     */
+    public Set<String> messageTypes() {
+        return Collections.unmodifiableSet(handlerMap.keySet());
+    }
+
+    /**
+     * Processes a JSON message from the user interface client.
+     *
+     * @param message JSON message
+     */
+    public void process(ObjectNode message) {
+        String type = JsonUtils.eventType(message);
+        long sid = JsonUtils.sid(message);
+        ObjectNode payload = JsonUtils.payload(message);
+        exec(type, sid, payload);
+    }
+
+    /**
+     * Finds the appropriate handler and executes the process method.
+     *
+     * @param eventType event type
+     * @param sid       sequence identifier
+     * @param payload   message payload
+     */
+    void exec(String eventType, long sid, ObjectNode payload) {
+        RequestHandler handler = handlerMap.get(eventType);
+        if (handler != null) {
+            handler.process(sid, payload);
+        }
+    }
+
+    /**
+     * Initializes the handler with the user interface connection and
+     * service directory context.
+     *
+     * @param connection user interface connection
+     * @param directory  service directory
+     */
+    public void init(UiConnection connection, ServiceDirectory directory) {
+        this.connection = connection;
+        this.directory = directory;
+        bindHandlers();
+    }
+
+    /**
+     * Destroys the message handler context.
+     */
+    public void destroy() {
+        this.connection = null;
+        this.directory = null;
+    }
+
+    /**
+     * Returns the user interface connection with which this handler was primed.
+     *
+     * @return user interface connection
+     */
+    public UiConnection connection() {
+        return connection;
+    }
+
+    /**
+     * Returns the user interface connection with which this handler was primed.
+     *
+     * @return user interface connection
+     */
+    public ServiceDirectory directory() {
+        return directory;
+    }
+
+    /**
+     * Returns implementation of the specified service class.
+     *
+     * @param serviceClass service class
+     * @param <T>          type of service
+     * @return implementation class
+     * @throws org.onlab.osgi.ServiceNotFoundException if no implementation found
+     */
+    protected <T> T get(Class<T> serviceClass) {
+        return directory.get(serviceClass);
+    }
+
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/table/AbstractTableRow.java b/core/api/src/main/java/org/onosproject/ui/table/AbstractTableRow.java
index 32a4396..5dd11a4 100644
--- a/core/api/src/main/java/org/onosproject/ui/table/AbstractTableRow.java
+++ b/core/api/src/main/java/org/onosproject/ui/table/AbstractTableRow.java
@@ -73,4 +73,19 @@
     protected void add(String id, Object value) {
         cells.put(id, value.toString());
     }
+
+    /**
+     * 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/ApplicationViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/ApplicationViewMessageHandler.java
index 08408dc..2924c8b 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
@@ -22,13 +22,15 @@
 import org.onosproject.app.ApplicationState;
 import org.onosproject.core.Application;
 import org.onosproject.core.ApplicationId;
-import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 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.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -37,62 +39,74 @@
 /**
  * Message handler for application view related messages.
  */
-public class ApplicationViewMessageHandler extends UiMessageHandler {
+public class ApplicationViewMessageHandler extends UiMessageHandlerTwo {
 
-    /**
-     * Creates a new message handler for the application messages.
-     */
-    protected ApplicationViewMessageHandler() {
-        super(ImmutableSet.of("appDataRequest", "appManagementRequest"));
-    }
+    private static final String APP_DATA_REQ = "appDataRequest";
+    private static final String APP_MGMT_REQ = "appManagementRequest";
 
     @Override
-    public void process(ObjectNode message) {
-        String type = eventType(message);
-        if (type.equals("appDataRequest")) {
-            sendAppList(message);
-        } else if (type.equals("appManagementRequest")) {
-            processManagementCommand(message);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(
+                new AppDataRequest(),
+                new AppMgmtRequest()
+        );
+    }
+
+    // ======================================================================
+
+    private final class AppDataRequest extends RequestHandler {
+
+        private AppDataRequest() {
+            super(APP_DATA_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            RowComparator rc = TableUtils.createRowComparator(payload);
+
+            ApplicationService service = get(ApplicationService.class);
+            TableRow[] rows = generateTableRows(service);
+            Arrays.sort(rows, rc);
+            ObjectNode rootNode = MAPPER.createObjectNode();
+            rootNode.set("apps", TableUtils.generateArrayNode(rows));
+
+            sendMessage("appDataResponse", 0, rootNode);
+        }
+
+        private TableRow[] generateTableRows(ApplicationService service) {
+            List<TableRow> list = service.getApplications().stream()
+                    .map(application -> new ApplicationTableRow(service, application))
+                    .collect(Collectors.toList());
+            return list.toArray(new TableRow[list.size()]);
         }
     }
+    // ======================================================================
 
-    private void sendAppList(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        RowComparator rc = TableUtils.createRowComparator(payload);
+    private final class AppMgmtRequest extends RequestHandler {
 
-        ApplicationService service = get(ApplicationService.class);
-        TableRow[] rows = generateTableRows(service);
-        Arrays.sort(rows, rc);
-        ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("apps", TableUtils.generateArrayNode(rows));
+        private AppMgmtRequest() {
+            super(APP_MGMT_REQ);
+        }
 
-        connection().sendMessage("appDataResponse", 0, rootNode);
-    }
-
-    private void processManagementCommand(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        String action = string(payload, "action");
-        String name = string(payload, "name");
-        if (action != null && name != null) {
-            ApplicationAdminService service = get(ApplicationAdminService.class);
-            ApplicationId appId = service.getId(name);
-            if (action.equals("activate")) {
-                service.activate(appId);
-            } else if (action.equals("deactivate")) {
-                service.deactivate(appId);
-            } else if (action.equals("uninstall")) {
-                service.uninstall(appId);
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            String action = string(payload, "action");
+            String name = string(payload, "name");
+            if (action != null && name != null) {
+                ApplicationAdminService service = get(ApplicationAdminService.class);
+                ApplicationId appId = service.getId(name);
+                if (action.equals("activate")) {
+                    service.activate(appId);
+                } else if (action.equals("deactivate")) {
+                    service.deactivate(appId);
+                } else if (action.equals("uninstall")) {
+                    service.uninstall(appId);
+                }
+                chain(APP_DATA_REQ, sid, payload);
             }
-            sendAppList(message);
         }
     }
-
-    private TableRow[] generateTableRows(ApplicationService service) {
-        List<TableRow> list = service.getApplications().stream()
-                .map(application -> new ApplicationTableRow(service, application))
-                .collect(Collectors.toList());
-        return list.toArray(new TableRow[list.size()]);
-    }
+    // ======================================================================
 
     /**
      * TableRow implementation for
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 55592e3..ee2dcd5 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
@@ -23,57 +23,61 @@
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.cluster.NodeId;
-import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 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;
+import java.util.Collection;
 import java.util.List;
+import java.util.stream.Collectors;
 
 
 /**
  * Message handler for cluster view related messages.
  */
-public class ClusterViewMessageHandler extends UiMessageHandler {
+public class ClusterViewMessageHandler extends UiMessageHandlerTwo {
 
-    /**
-     * Creates a new message handler for the cluster messages.
-     */
-    protected ClusterViewMessageHandler() {
-        super(ImmutableSet.of("clusterDataRequest"));
-    }
+    private static final String CLUSTER_DATA_REQ = "clusterDataRequest";
 
     @Override
-    public void process(ObjectNode message) {
-        String type = eventType(message);
-        if (type.equals("clusterDataRequest")) {
-            sendClusterList(message);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(new ClusterDataRequest());
+    }
+
+    // ======================================================================
+
+    private final class ClusterDataRequest extends RequestHandler {
+
+        private ClusterDataRequest() {
+            super(CLUSTER_DATA_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            RowComparator rc = TableUtils.createRowComparator(payload);
+
+            ClusterService service = get(ClusterService.class);
+            TableRow[] rows = generateTableRows(service);
+            Arrays.sort(rows, rc);
+            ObjectNode rootNode = MAPPER.createObjectNode();
+            rootNode.set("clusters", TableUtils.generateArrayNode(rows));
+
+            sendMessage("clusterDataResponse", 0, rootNode);
+        }
+
+        private TableRow[] generateTableRows(ClusterService service) {
+            List<TableRow> list = service.getNodes().stream()
+                    .map(node -> new ControllerNodeTableRow(service, node))
+                    .collect(Collectors.toList());
+            return list.toArray(new TableRow[list.size()]);
         }
     }
 
-    private void sendClusterList(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        RowComparator rc = TableUtils.createRowComparator(payload);
-
-        ClusterService service = get(ClusterService.class);
-        TableRow[] rows = generateTableRows(service);
-        Arrays.sort(rows, rc);
-        ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("clusters", TableUtils.generateArrayNode(rows));
-
-        connection().sendMessage("clusterDataResponse", 0, rootNode);
-    }
-
-    private TableRow[] generateTableRows(ClusterService service) {
-        List<TableRow> list = new ArrayList<>();
-        for (ControllerNode node : service.getNodes()) {
-            list.add(new ControllerNodeTableRow(service, node));
-        }
-        return list.toArray(new TableRow[list.size()]);
-    }
+    // ======================================================================
 
     /**
      * TableRow implementation for {@link ControllerNode controller nodes}.
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 5015cb5..302863b 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,7 +27,8 @@
 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.RequestHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.onosproject.ui.table.AbstractTableRow;
 import org.onosproject.ui.table.RowComparator;
 import org.onosproject.ui.table.TableRow;
@@ -35,6 +36,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Set;
@@ -42,7 +44,10 @@
 /**
  * Message handler for device view related messages.
  */
-public class DeviceViewMessageHandler extends UiMessageHandler {
+public class DeviceViewMessageHandler extends UiMessageHandlerTwo {
+
+    private static final String DEV_DATA_REQ = "deviceDataRequest";
+    private static final String DEV_DETAIL_REQ = "deviceDetailRequest";
 
     private static final String ID = "id";
     private static final String TYPE = "type";
@@ -65,110 +70,120 @@
     private static final String NAME = "name";
 
 
-    /**
-     * Creates a new message handler for the device messages.
-     */
-    protected DeviceViewMessageHandler() {
-        super(ImmutableSet.of("deviceDataRequest", "deviceDetailsRequest"));
-    }
-
     @Override
-    public void process(ObjectNode message) {
-        String type = eventType(message);
-        if (type.equals("deviceDataRequest")) {
-            dataRequest(message);
-        } else if (type.equals("deviceDetailsRequest")) {
-            detailsRequest(message);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(
+                new DataRequestHandler(),
+                new DetailRequestHandler()
+        );
+    }
+
+    // ======================================================================
+
+    private final class DataRequestHandler extends RequestHandler {
+
+        private DataRequestHandler() {
+            super(DEV_DATA_REQ);
         }
-    }
 
-    private void dataRequest(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        RowComparator rc = TableUtils.createRowComparator(payload);
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            RowComparator rc = TableUtils.createRowComparator(payload);
 
-        DeviceService service = get(DeviceService.class);
-        MastershipService mastershipService = get(MastershipService.class);
-        TableRow[] rows = generateTableRows(service, mastershipService);
-        Arrays.sort(rows, rc);
-        ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("devices", TableUtils.generateArrayNode(rows));
+            DeviceService service = get(DeviceService.class);
+            MastershipService mastershipService = get(MastershipService.class);
+            TableRow[] rows = generateTableRows(service, mastershipService);
+            Arrays.sort(rows, rc);
+            ObjectNode rootNode = MAPPER.createObjectNode();
+            rootNode.set("devices", TableUtils.generateArrayNode(rows));
 
-        connection().sendMessage("deviceDataResponse", 0, rootNode);
-    }
-
-    private void detailsRequest(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        String id = string(payload, "id", "of:0000000000000000");
-
-        DeviceId deviceId = DeviceId.deviceId(id);
-        DeviceService service = get(DeviceService.class);
-        MastershipService ms = get(MastershipService.class);
-        Device device = service.getDevice(deviceId);
-        ObjectNode data = mapper.createObjectNode();
-
-        data.put(ID, deviceId.toString());
-        data.put(TYPE, device.type().toString());
-        data.put(TYPE_IID, getTypeIconId(device));
-        data.put(MFR, device.manufacturer());
-        data.put(HW, device.hwVersion());
-        data.put(SW, device.swVersion());
-        data.put(SERIAL, device.serialNumber());
-        data.put(CHASSIS_ID, device.chassisId().toString());
-        data.put(MASTER_ID, ms.getMasterFor(deviceId).toString());
-        data.put(PROTOCOL, device.annotations().value(PROTOCOL));
-
-        ArrayNode ports = mapper.createArrayNode();
-
-        List<Port> portList = new ArrayList<>(service.getPorts(deviceId));
-        Collections.sort(portList, (p1, p2) -> {
-            long delta = p1.number().toLong() - p2.number().toLong();
-            return delta == 0 ? 0 : (delta < 0 ? -1 : +1);
-        });
-
-        for (Port p : portList) {
-            ports.add(portData(p, deviceId));
+            sendMessage("deviceDataResponse", 0, rootNode);
         }
-        data.set(PORTS, ports);
 
-        ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("details", data);
-        connection().sendMessage("deviceDetailsResponse", 0, rootNode);
-    }
-
-    private TableRow[] generateTableRows(DeviceService service,
-                                         MastershipService mastershipService) {
-        List<TableRow> list = new ArrayList<>();
-        for (Device dev : service.getDevices()) {
-            list.add(new DeviceTableRow(service, mastershipService, dev));
-        }
-        return list.toArray(new TableRow[list.size()]);
-    }
-
-    private ObjectNode portData(Port p, DeviceId id) {
-        ObjectNode port = mapper.createObjectNode();
-        LinkService ls = get(LinkService.class);
-        String name = p.annotations().value(AnnotationKeys.PORT_NAME);
-
-        port.put(ID, p.number().toString());
-        port.put(TYPE, p.type().toString());
-        port.put(SPEED, p.portSpeed());
-        port.put(ENABLED, p.isEnabled());
-        port.put(NAME, name != null ? name : "");
-
-        Set<Link> links = ls.getEgressLinks(new ConnectPoint(id, p.number()));
-        if (!links.isEmpty()) {
-            StringBuilder egressLinks = new StringBuilder();
-            for (Link l : links) {
-                ConnectPoint dest = l.dst();
-                egressLinks.append(dest.elementId()).append("/")
-                        .append(dest.port()).append(" ");
+        private TableRow[] generateTableRows(DeviceService service,
+                                             MastershipService mastershipService) {
+            List<TableRow> list = new ArrayList<>();
+            for (Device dev : service.getDevices()) {
+                list.add(new DeviceTableRow(service, mastershipService, dev));
             }
-            port.put(LINK_DEST, egressLinks.toString());
+            return list.toArray(new TableRow[list.size()]);
+        }
+    }
+
+    // ======================================================================
+
+    private final class DetailRequestHandler extends RequestHandler {
+        private DetailRequestHandler() {
+            super(DEV_DETAIL_REQ);
         }
 
-        return port;
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            String id = string(payload, "id", "of:0000000000000000");
+
+            DeviceId deviceId = DeviceId.deviceId(id);
+            DeviceService service = get(DeviceService.class);
+            MastershipService ms = get(MastershipService.class);
+            Device device = service.getDevice(deviceId);
+            ObjectNode data = MAPPER.createObjectNode();
+
+            data.put(ID, deviceId.toString());
+            data.put(TYPE, device.type().toString());
+            data.put(TYPE_IID, getTypeIconId(device));
+            data.put(MFR, device.manufacturer());
+            data.put(HW, device.hwVersion());
+            data.put(SW, device.swVersion());
+            data.put(SERIAL, device.serialNumber());
+            data.put(CHASSIS_ID, device.chassisId().toString());
+            data.put(MASTER_ID, ms.getMasterFor(deviceId).toString());
+            data.put(PROTOCOL, device.annotations().value(PROTOCOL));
+
+            ArrayNode ports = MAPPER.createArrayNode();
+
+            List<Port> portList = new ArrayList<>(service.getPorts(deviceId));
+            Collections.sort(portList, (p1, p2) -> {
+                long delta = p1.number().toLong() - p2.number().toLong();
+                return delta == 0 ? 0 : (delta < 0 ? -1 : +1);
+            });
+
+            for (Port p : portList) {
+                ports.add(portData(p, deviceId));
+            }
+            data.set(PORTS, ports);
+
+            ObjectNode rootNode = MAPPER.createObjectNode();
+            rootNode.set("details", data);
+            sendMessage("deviceDetailsResponse", 0, rootNode);
+        }
+
+        private ObjectNode portData(Port p, DeviceId id) {
+            ObjectNode port = MAPPER.createObjectNode();
+            LinkService ls = get(LinkService.class);
+            String name = p.annotations().value(AnnotationKeys.PORT_NAME);
+
+            port.put(ID, p.number().toString());
+            port.put(TYPE, p.type().toString());
+            port.put(SPEED, p.portSpeed());
+            port.put(ENABLED, p.isEnabled());
+            port.put(NAME, name != null ? name : "");
+
+            Set<Link> links = ls.getEgressLinks(new ConnectPoint(id, p.number()));
+            if (!links.isEmpty()) {
+                StringBuilder egressLinks = new StringBuilder();
+                for (Link l : links) {
+                    ConnectPoint dest = l.dst();
+                    egressLinks.append(dest.elementId()).append("/")
+                            .append(dest.port()).append(" ");
+                }
+                port.put(LINK_DEST, egressLinks.toString());
+            }
+
+            return port;
+        }
+
     }
 
+
     private static String getTypeIconId(Device d) {
         return DEV_ICON_PREFIX + d.type().toString();
     }
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 81965da..0ad1d70 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
@@ -26,7 +26,8 @@
 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.RequestHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.onosproject.ui.table.AbstractTableRow;
 import org.onosproject.ui.table.RowComparator;
 import org.onosproject.ui.table.TableRow;
@@ -34,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
@@ -41,54 +43,57 @@
 /**
  * Message handler for flow view related messages.
  */
-public class FlowViewMessageHandler extends UiMessageHandler {
+public class FlowViewMessageHandler extends UiMessageHandlerTwo {
+
+    private static final String FLOW_DATA_REQ = "flowDataRequest";
 
     private static final String NO_DEV = "none";
 
-    /**
-     * Creates a new message handler for the flow messages.
-     */
-    protected FlowViewMessageHandler() {
-        super(ImmutableSet.of("flowDataRequest"));
-    }
-
     @Override
-    public void process(ObjectNode message) {
-        String type = eventType(message);
-        if (type.equals("flowDataRequest")) {
-            sendFlowList(message);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(new FlowDataRequest());
+    }
+
+    // ======================================================================
+
+    private final class FlowDataRequest extends RequestHandler {
+
+        private FlowDataRequest() {
+            super(FLOW_DATA_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            RowComparator rc = TableUtils.createRowComparator(payload);
+            String uri = string(payload, "devId", NO_DEV);
+
+            ObjectNode rootNode;
+            if (uri.equals(NO_DEV)) {
+                rootNode = MAPPER.createObjectNode();
+                rootNode.set("flows", MAPPER.createArrayNode());
+            } else {
+                DeviceId deviceId = DeviceId.deviceId(uri);
+                FlowRuleService service = get(FlowRuleService.class);
+                TableRow[] rows = generateTableRows(service, deviceId);
+                Arrays.sort(rows, rc);
+                rootNode = MAPPER.createObjectNode();
+                rootNode.set("flows", TableUtils.generateArrayNode(rows));
+            }
+
+            sendMessage("flowDataResponse", 0, rootNode);
+        }
+
+        private TableRow[] generateTableRows(FlowRuleService service,
+                                             DeviceId deviceId) {
+            List<TableRow> list = new ArrayList<>();
+            for (FlowEntry flow : service.getFlowEntries(deviceId)) {
+                list.add(new FlowTableRow(flow));
+            }
+            return list.toArray(new TableRow[list.size()]);
         }
     }
 
-    private void sendFlowList(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        RowComparator rc = TableUtils.createRowComparator(payload);
-        String uri = string(payload, "devId", NO_DEV);
-
-        ObjectNode rootNode;
-        if (uri.equals(NO_DEV)) {
-            rootNode = mapper.createObjectNode();
-            rootNode.set("flows", mapper.createArrayNode());
-        } else {
-            DeviceId deviceId = DeviceId.deviceId(uri);
-            FlowRuleService service = get(FlowRuleService.class);
-            TableRow[] rows = generateTableRows(service, deviceId);
-            Arrays.sort(rows, rc);
-            rootNode = mapper.createObjectNode();
-            rootNode.set("flows", TableUtils.generateArrayNode(rows));
-        }
-
-        connection().sendMessage("flowDataResponse", 0, rootNode);
-    }
-
-    private TableRow[] generateTableRows(FlowRuleService service,
-                                         DeviceId deviceId) {
-        List<TableRow> list = new ArrayList<>();
-        for (FlowEntry flow : service.getFlowEntries(deviceId)) {
-            list.add(new FlowTableRow(flow));
-        }
-        return list.toArray(new TableRow[list.size()]);
-    }
+    // ======================================================================
 
     /**
      * TableRow implementation for {@link org.onosproject.net.flow.FlowRule flows}.
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 472ae16..a4127e0 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
@@ -21,7 +21,8 @@
 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.RequestHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.onosproject.ui.table.AbstractTableRow;
 import org.onosproject.ui.table.RowComparator;
 import org.onosproject.ui.table.TableRow;
@@ -29,6 +30,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 
 import static com.google.common.base.Strings.isNullOrEmpty;
@@ -36,43 +38,47 @@
 /**
  * Message handler for host view related messages.
  */
-public class HostViewMessageHandler extends UiMessageHandler {
+public class HostViewMessageHandler extends UiMessageHandlerTwo {
 
-    /**
-     * Creates a new message handler for the host messages.
-     */
-    protected HostViewMessageHandler() {
-        super(ImmutableSet.of("hostDataRequest"));
-    }
+    private static final String HOST_DATA_REQ = "hostDataRequest";
+
 
     @Override
-    public void process(ObjectNode message) {
-        String type = eventType(message);
-        if (type.equals("hostDataRequest")) {
-            sendHostList(message);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(new HostDataRequest());
+    }
+
+    // ======================================================================
+
+    private final class HostDataRequest extends RequestHandler {
+
+        private HostDataRequest() {
+            super(HOST_DATA_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            RowComparator rc = TableUtils.createRowComparator(payload);
+
+            HostService service = get(HostService.class);
+            TableRow[] rows = generateTableRows(service);
+            Arrays.sort(rows, rc);
+            ObjectNode rootNode = MAPPER.createObjectNode();
+            rootNode.set("hosts", TableUtils.generateArrayNode(rows));
+
+            sendMessage("hostDataResponse", 0, rootNode);
+        }
+
+        private TableRow[] generateTableRows(HostService service) {
+            List<TableRow> list = new ArrayList<>();
+            for (Host host : service.getHosts()) {
+                list.add(new HostTableRow(host));
+            }
+            return list.toArray(new TableRow[list.size()]);
         }
     }
 
-    private void sendHostList(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        RowComparator rc = TableUtils.createRowComparator(payload);
-
-        HostService service = get(HostService.class);
-        TableRow[] rows = generateTableRows(service);
-        Arrays.sort(rows, rc);
-        ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("hosts", TableUtils.generateArrayNode(rows));
-
-        connection().sendMessage("hostDataResponse", 0, rootNode);
-    }
-
-    private TableRow[] generateTableRows(HostService service) {
-        List<TableRow> list = new ArrayList<>();
-        for (Host host : service.getHosts()) {
-            list.add(new HostTableRow(host));
-        }
-        return list.toArray(new TableRow[list.size()]);
-    }
+    // ======================================================================
 
     /**
      * TableRow implementation for {@link Host hosts}.
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 4924d4b..539430a 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
@@ -31,7 +31,8 @@
 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.RequestHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.onosproject.ui.table.AbstractTableRow;
 import org.onosproject.ui.table.RowComparator;
 import org.onosproject.ui.table.TableRow;
@@ -39,49 +40,54 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 
 /**
  * Message handler for intent view related messages.
  */
-public class IntentViewMessageHandler extends UiMessageHandler {
+public class IntentViewMessageHandler extends UiMessageHandlerTwo {
 
-    /**
-     * Creates a new message handler for the intent messages.
-     */
-    protected IntentViewMessageHandler() {
-        super(ImmutableSet.of("intentDataRequest"));
-    }
+    private static final String INTENT_DATA_REQ = "intentDataRequest";
+
 
     @Override
-    public void process(ObjectNode message) {
-        String type = eventType(message);
-        if (type.equals("intentDataRequest")) {
-            sendIntentList(message);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(new IntentDataRequest());
+    }
+
+    // ======================================================================
+
+    private final class IntentDataRequest extends RequestHandler {
+
+        private IntentDataRequest() {
+            super(INTENT_DATA_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            RowComparator rc = TableUtils.createRowComparator(payload);
+
+            IntentService service = get(IntentService.class);
+            TableRow[] rows = generateTableRows(service);
+            Arrays.sort(rows, rc);
+            ObjectNode rootNode = MAPPER.createObjectNode();
+            rootNode.set("intents", TableUtils.generateArrayNode(rows));
+
+            sendMessage("intentDataResponse", 0, rootNode);
+        }
+
+        private TableRow[] generateTableRows(IntentService service) {
+            List<TableRow> list = new ArrayList<>();
+            for (Intent intent : service.getIntents()) {
+                list.add(new IntentTableRow(intent));
+            }
+            return list.toArray(new TableRow[list.size()]);
         }
     }
 
-    private void sendIntentList(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        RowComparator rc = TableUtils.createRowComparator(payload);
-
-        IntentService service = get(IntentService.class);
-        TableRow[] rows = generateTableRows(service);
-        Arrays.sort(rows, rc);
-        ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("intents", TableUtils.generateArrayNode(rows));
-
-        connection().sendMessage("intentDataResponse", 0, rootNode);
-    }
-
-    private TableRow[] generateTableRows(IntentService service) {
-        List<TableRow> list = new ArrayList<>();
-        for (Intent intent : service.getIntents()) {
-            list.add(new IntentTableRow(intent));
-        }
-        return list.toArray(new TableRow[list.size()]);
-    }
+    // ======================================================================
 
     /**
      * TableRow implementation for {@link Intent intents}.
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 6683188..55291c4 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
@@ -23,7 +23,8 @@
 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.RequestHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.onosproject.ui.impl.TopologyViewMessageHandlerBase.BiLink;
 import org.onosproject.ui.table.AbstractTableRow;
 import org.onosproject.ui.table.RowComparator;
@@ -32,6 +33,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
@@ -40,47 +42,51 @@
 /**
  * Message handler for link view related messages.
  */
-public class LinkViewMessageHandler extends UiMessageHandler {
+public class LinkViewMessageHandler extends UiMessageHandlerTwo {
 
-    /**
-     * Creates a new message handler for the link messages.
-     */
-    protected LinkViewMessageHandler() {
-        super(ImmutableSet.of("linkDataRequest"));
-    }
+    private static final String LINK_DATA_REQ = "linkDataRequest";
+
 
     @Override
-    public void process(ObjectNode message) {
-        String type = eventType(message);
-        if (type.equals("linkDataRequest")) {
-            sendLinkList(message);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(new LinkDataRequest());
+    }
+
+    // ======================================================================
+
+    private final class LinkDataRequest extends RequestHandler {
+
+        private LinkDataRequest() {
+            super(LINK_DATA_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            RowComparator rc = TableUtils.createRowComparator(payload, "one");
+
+            LinkService service = get(LinkService.class);
+            TableRow[] rows = generateTableRows(service);
+            Arrays.sort(rows, rc);
+            ObjectNode rootNode = MAPPER.createObjectNode();
+            rootNode.set("links", TableUtils.generateArrayNode(rows));
+
+            sendMessage("linkDataResponse", 0, rootNode);
+        }
+
+        private TableRow[] generateTableRows(LinkService service) {
+            List<TableRow> list = new ArrayList<>();
+
+            // First consolidate all uni-directional links into two-directional ones.
+            Map<LinkKey, BiLink> biLinks = Maps.newHashMap();
+            service.getLinks().forEach(link -> addLink(biLinks, link));
+
+            // Now scan over all bi-links and produce table rows from them.
+            biLinks.values().forEach(biLink -> list.add(new LinkTableRow(biLink)));
+            return list.toArray(new TableRow[list.size()]);
         }
     }
 
-    private void sendLinkList(ObjectNode message) {
-        ObjectNode payload = payload(message);
-        RowComparator rc = TableUtils.createRowComparator(payload, "one");
-
-        LinkService service = get(LinkService.class);
-        TableRow[] rows = generateTableRows(service);
-        Arrays.sort(rows, rc);
-        ObjectNode rootNode = mapper.createObjectNode();
-        rootNode.set("links", TableUtils.generateArrayNode(rows));
-
-        connection().sendMessage("linkDataResponse", 0, rootNode);
-    }
-
-    private TableRow[] generateTableRows(LinkService service) {
-        List<TableRow> list = new ArrayList<>();
-
-        // First consolidate all uni-directional links into two-directional ones.
-        Map<LinkKey, BiLink> biLinks = Maps.newHashMap();
-        service.getLinks().forEach(link -> addLink(biLinks, link));
-
-        // Now scan over all bi-links and produce table rows from them.
-        biLinks.values().forEach(biLink -> list.add(new LinkTableRow(biLink)));
-        return list.toArray(new TableRow[list.size()]);
-    }
+    // ======================================================================
 
     /**
      * TableRow implementation for {@link org.onosproject.net.Link links}.
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 8ba8d3a..9e0339e 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
@@ -54,9 +54,12 @@
 import org.onosproject.net.intent.MultiPointToSinglePointIntent;
 import org.onosproject.net.link.LinkEvent;
 import org.onosproject.net.link.LinkListener;
+import org.onosproject.ui.JsonUtils;
+import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -79,6 +82,26 @@
  */
 public class TopologyViewMessageHandler extends TopologyViewMessageHandlerBase {
 
+    private static final String REQ_DETAILS = "requestDetails";
+    private static final String UPDATE_META = "updateMeta";
+    private static final String ADD_HOST_INTENT = "addHostIntent";
+    private static final String ADD_MULTI_SRC_INTENT = "addMultiSourceIntent";
+    private static final String REQ_RELATED_INTENTS = "requestRelatedIntents";
+    private static final String REQ_NEXT_INTENT = "requestNextRelatedIntent";
+    private static final String REQ_PREV_INTENT = "requestPrevRelatedIntent";
+    private static final String REQ_SEL_INTENT_TRAFFIC = "requestSelectedIntentTraffic";
+    private static final String REQ_ALL_TRAFFIC = "requestAllTraffic";
+    private static final String REQ_DEV_LINK_FLOWS = "requestDeviceLinkFlows";
+    private static final String CANCEL_TRAFFIC = "cancelTraffic";
+    private static final String REQ_SUMMARY = "requestSummary";
+    private static final String CANCEL_SUMMARY = "cancelSummary";
+    private static final String EQ_MASTERS = "equalizeMasters";
+    private static final String SPRITE_LIST_REQ = "spriteListRequest";
+    private static final String SPRITE_DATA_REQ = "spriteDataRequest";
+    private static final String TOPO_START = "topoStart";
+    private static final String TOPO_STOP = "topoStop";
+
+
     private static final String APP_ID = "org.onosproject.gui";
 
     private static final long TRAFFIC_FREQUENCY = 5000;
@@ -111,11 +134,11 @@
 
     private final Accumulator<Event> eventAccummulator = new InternalEventAccummulator();
 
-    private TimerTask trafficTask;
-    private ObjectNode trafficEvent;
+    private TimerTask trafficTask = null;
+    private TrafficEvent trafficEvent = null;
 
-    private TimerTask summaryTask;
-    private ObjectNode summaryEvent;
+    private TimerTask summaryTask = null;
+    private boolean summaryRunning = false;
 
     private boolean listenersRemoved = false;
 
@@ -127,30 +150,6 @@
     private List<Intent> selectedIntents;
     private int currentIntentIndex = -1;
 
-    /**
-     * Creates a new web-socket for serving data to GUI topology view.
-     */
-    public TopologyViewMessageHandler() {
-        super(ImmutableSet.of("topoStart",
-                              "topoStop",
-                              "requestDetails",
-                              "updateMeta",
-                              "addHostIntent",
-                              "addMultiSourceIntent",
-                              "requestRelatedIntents",
-                              "requestNextRelatedIntent",
-                              "requestPrevRelatedIntent",
-                              "requestSelectedIntentTraffic",
-                              "requestAllTraffic",
-                              "requestDeviceLinkFlows",
-                              "cancelTraffic",
-                              "requestSummary",
-                              "cancelSummary",
-                              "equalizeMasters",
-                              "spriteListRequest",
-                              "spriteDataRequest"
-        ));
-    }
 
     @Override
     public void init(UiConnection connection, ServiceDirectory directory) {
@@ -167,66 +166,326 @@
         super.destroy();
     }
 
-    // Processes the specified event.
     @Override
-    public void process(ObjectNode event) {
-        String type = string(event, "event", "unknown");
-        if (type.equals("requestDetails")) {
-            requestDetails(event);
-        } else if (type.equals("updateMeta")) {
-            updateMetaUi(event);
+    protected Collection<RequestHandler> getHandlers() {
+        return ImmutableSet.of(
+                new TopoStart(),
+                new TopoStop(),
+                new ReqSummary(),
+                new CancelSummary(),
+                new SpriteListReq(),
+                new SpriteDataReq(),
+                new RequestDetails(),
+                new UpdateMeta(),
+                new EqMasters(),
 
-        } else if (type.equals("addHostIntent")) {
-            createHostIntent(event);
-        } else if (type.equals("addMultiSourceIntent")) {
-            createMultiSourceIntent(event);
+                // TODO: migrate traffic related to separate app
+                new AddHostIntent(),
+                new AddMultiSourceIntent(),
+                new ReqRelatedIntents(),
+                new ReqNextIntent(),
+                new ReqPrevIntent(),
+                new ReqSelectedIntentTraffic(),
+                new ReqAllTraffic(),
+                new ReqDevLinkFlows(),
+                new CancelTraffic()
+        );
+    }
 
-        } else if (type.equals("requestRelatedIntents")) {
-            stopTrafficMonitoring();
-            requestRelatedIntents(event);
+    // ==================================================================
 
-        } else if (type.equals("requestNextRelatedIntent")) {
-            stopTrafficMonitoring();
-            requestAnotherRelatedIntent(event, +1);
-        } else if (type.equals("requestPrevRelatedIntent")) {
-            stopTrafficMonitoring();
-            requestAnotherRelatedIntent(event, -1);
-        } else if (type.equals("requestSelectedIntentTraffic")) {
-            requestSelectedIntentTraffic(event);
-            startTrafficMonitoring(event);
+    private final class TopoStart extends RequestHandler {
+        private TopoStart() {
+            super(TOPO_START);
+        }
 
-        } else if (type.equals("requestAllTraffic")) {
-            requestAllTraffic(event);
-            startTrafficMonitoring(event);
-
-        } else if (type.equals("requestDeviceLinkFlows")) {
-            requestDeviceLinkFlows(event);
-            startTrafficMonitoring(event);
-
-        } else if (type.equals("cancelTraffic")) {
-            cancelTraffic(event);
-
-        } else if (type.equals("requestSummary")) {
-            requestSummary(event);
-            startSummaryMonitoring(event);
-        } else if (type.equals("cancelSummary")) {
-            stopSummaryMonitoring();
-
-        } else if (type.equals("equalizeMasters")) {
-            equalizeMasters(event);
-
-        } else if (type.equals("spriteListRequest")) {
-            sendSpriteList(event);
-        } else if (type.equals("spriteDataRequest")) {
-            sendSpriteData(event);
-
-        } else if (type.equals("topoStart")) {
-            sendAllInitialData();
-        } else if (type.equals("topoStop")) {
-            cancelAllRequests();
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            addListeners();
+            sendAllInstances(null);
+            sendAllDevices();
+            sendAllLinks();
+            sendAllHosts();
         }
     }
 
+    private final class TopoStop extends RequestHandler {
+        private TopoStop() {
+            super(TOPO_STOP);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            stopSummaryMonitoring();
+            stopTrafficMonitoring();
+        }
+    }
+
+    private final class ReqSummary extends RequestHandler {
+        private ReqSummary() {
+            super(REQ_SUMMARY);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            requestSummary(sid);
+            startSummaryMonitoring();
+        }
+    }
+
+    private final class CancelSummary extends RequestHandler {
+        private CancelSummary() {
+            super(CANCEL_SUMMARY);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            stopSummaryMonitoring();
+        }
+    }
+
+    private final class SpriteListReq extends RequestHandler {
+        private SpriteListReq() {
+            super(SPRITE_LIST_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            ObjectNode root = mapper.createObjectNode();
+            ArrayNode names = mapper.createArrayNode();
+            get(SpriteService.class).getNames().forEach(names::add);
+            root.set("names", names);
+            sendMessage("spriteListResponse", sid, root);
+        }
+    }
+
+    private final class SpriteDataReq extends RequestHandler {
+        private SpriteDataReq() {
+            super(SPRITE_DATA_REQ);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            String name = string(payload, "name");
+            ObjectNode root = mapper.createObjectNode();
+            root.set("data", get(SpriteService.class).get(name));
+            sendMessage("spriteDataResponse", sid, root);
+        }
+    }
+
+    private final class RequestDetails extends RequestHandler {
+        private RequestDetails() {
+            super(REQ_DETAILS);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            String type = string(payload, "class", "unknown");
+            String id = JsonUtils.string(payload, "id");
+
+            if (type.equals("device")) {
+                sendMessage(deviceDetails(deviceId(id), sid));
+            } else if (type.equals("host")) {
+                sendMessage(hostDetails(hostId(id), sid));
+            }
+        }
+    }
+
+    private final class UpdateMeta extends RequestHandler {
+        private UpdateMeta() {
+            super(UPDATE_META);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            updateMetaUi(payload);
+        }
+    }
+
+    private final class EqMasters extends RequestHandler {
+        private EqMasters() {
+            super(EQ_MASTERS);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            directory.get(MastershipAdminService.class).balanceRoles();
+        }
+    }
+
+    // === TODO: move traffic related classes to traffic app
+
+    private final class AddHostIntent extends RequestHandler {
+        private AddHostIntent() {
+            super(ADD_HOST_INTENT);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            // TODO: add protection against device ids and non-existent hosts.
+            HostId one = hostId(string(payload, "one"));
+            HostId two = hostId(string(payload, "two"));
+
+            HostToHostIntent intent = HostToHostIntent.builder()
+                            .appId(appId)
+                            .one(one)
+                            .two(two)
+                            .build();
+
+            intentService.submit(intent);
+            startMonitoringIntent(intent);
+        }
+    }
+
+    private final class AddMultiSourceIntent extends RequestHandler {
+        private AddMultiSourceIntent() {
+            super(ADD_MULTI_SRC_INTENT);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            // TODO: add protection against device ids and non-existent hosts.
+            Set<HostId> src = getHostIds((ArrayNode) payload.path("src"));
+            HostId dst = hostId(string(payload, "dst"));
+            Host dstHost = hostService.getHost(dst);
+
+            Set<ConnectPoint> ingressPoints = getHostLocations(src);
+
+            // FIXME: clearly, this is not enough
+            TrafficSelector selector = DefaultTrafficSelector.builder()
+                    .matchEthDst(dstHost.mac()).build();
+            TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
+
+            MultiPointToSinglePointIntent intent =
+                    MultiPointToSinglePointIntent.builder()
+                            .appId(appId)
+                            .selector(selector)
+                            .treatment(treatment)
+                            .ingressPoints(ingressPoints)
+                            .egressPoint(dstHost.location())
+                            .build();
+
+            intentService.submit(intent);
+            startMonitoringIntent(intent);
+        }
+    }
+
+    private final class ReqRelatedIntents extends RequestHandler {
+        private ReqRelatedIntents() {
+            super(REQ_RELATED_INTENTS);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            // Cancel any other traffic monitoring mode.
+            stopTrafficMonitoring();
+
+            if (!payload.has("ids")) {
+                return;
+            }
+
+            // Get the set of selected hosts and their intents.
+            ArrayNode ids = (ArrayNode) payload.path("ids");
+            selectedHosts = getHosts(ids);
+            selectedDevices = getDevices(ids);
+            selectedIntents = intentFilter.findPathIntents(
+                    selectedHosts, selectedDevices, intentService.getIntents());
+            currentIntentIndex = -1;
+
+            if (haveSelectedIntents()) {
+                // Send a message to highlight all links of all monitored intents.
+                sendMessage(trafficMessage(new TrafficClass("primary", selectedIntents)));
+            }
+
+            // TODO: Re-introduce once the client click vs hover gesture stuff is sorted out.
+//        String hover = string(payload, "hover");
+//        if (!isNullOrEmpty(hover)) {
+//            // If there is a hover node, include it in the selection and find intents.
+//            processHoverExtendedSelection(sid, hover);
+//        }
+        }
+    }
+
+    private final class ReqNextIntent extends RequestHandler {
+        private ReqNextIntent() {
+            super(REQ_NEXT_INTENT);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            stopTrafficMonitoring();
+            requestAnotherRelatedIntent(+1);
+        }
+    }
+
+    private final class ReqPrevIntent extends RequestHandler {
+        private ReqPrevIntent() {
+            super(REQ_PREV_INTENT);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            stopTrafficMonitoring();
+            requestAnotherRelatedIntent(-1);
+        }
+    }
+
+    private final class ReqSelectedIntentTraffic extends RequestHandler {
+        private ReqSelectedIntentTraffic() {
+            super(REQ_SEL_INTENT_TRAFFIC);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            trafficEvent =
+                    new TrafficEvent(TrafficEvent.Type.SEL_INTENT, payload);
+            requestSelectedIntentTraffic();
+            startTrafficMonitoring();
+        }
+    }
+
+    private final class ReqAllTraffic extends RequestHandler {
+        private ReqAllTraffic() {
+            super(REQ_ALL_TRAFFIC);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            trafficEvent =
+                    new TrafficEvent(TrafficEvent.Type.ALL_TRAFFIC, payload);
+            requestAllTraffic();
+        }
+    }
+
+    private final class ReqDevLinkFlows extends RequestHandler {
+        private ReqDevLinkFlows() {
+            super(REQ_DEV_LINK_FLOWS);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            trafficEvent =
+                    new TrafficEvent(TrafficEvent.Type.DEV_LINK_FLOWS, payload);
+            requestDeviceLinkFlows(payload);
+        }
+    }
+
+    private final class CancelTraffic extends RequestHandler {
+        private CancelTraffic() {
+            super(CANCEL_TRAFFIC);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            selectedIntents = null;
+            sendMessage(trafficMessage());
+            stopTrafficMonitoring();
+        }
+    }
+
+    //=======================================================================
+
+
     // Sends the specified data to the client.
     protected synchronized void sendMessage(ObjectNode data) {
         UiConnection connection = connection();
@@ -235,15 +494,12 @@
         }
     }
 
-    private void sendAllInitialData() {
-        addListeners();
-        sendAllInstances(null);
-        sendAllDevices();
-        sendAllLinks();
-        sendAllHosts();
-
+    // Subscribes for summary messages.
+    private synchronized void requestSummary(long sid) {
+        sendMessage(summmaryMessage(sid));
     }
 
+
     private void cancelAllRequests() {
         stopSummaryMonitoring();
         stopTrafficMonitoring();
@@ -296,77 +552,15 @@
         }
     }
 
-    // Sends back device or host details.
-    private void requestDetails(ObjectNode event) {
-        ObjectNode payload = payload(event);
-        String type = string(payload, "class", "unknown");
-        long sid = number(event, "sid");
 
-        if (type.equals("device")) {
-            sendMessage(deviceDetails(deviceId(string(payload, "id")), sid));
-        } else if (type.equals("host")) {
-            sendMessage(hostDetails(hostId(string(payload, "id")), sid));
-        }
-    }
-
-
-    // Creates host-to-host intent.
-    private void createHostIntent(ObjectNode event) {
-        ObjectNode payload = payload(event);
-        long id = number(event, "sid");
-        // TODO: add protection against device ids and non-existent hosts.
-        HostId one = hostId(string(payload, "one"));
-        HostId two = hostId(string(payload, "two"));
-
-        HostToHostIntent intent =
-                HostToHostIntent.builder()
-                        .appId(appId)
-                        .one(one)
-                        .two(two)
-                        .build();
-
-        intentService.submit(intent);
-        startMonitoringIntent(event, intent);
-    }
-
-    // Creates multi-source-to-single-dest intent.
-    private void createMultiSourceIntent(ObjectNode event) {
-        ObjectNode payload = payload(event);
-        long id = number(event, "sid");
-        // TODO: add protection against device ids and non-existent hosts.
-        Set<HostId> src = getHostIds((ArrayNode) payload.path("src"));
-        HostId dst = hostId(string(payload, "dst"));
-        Host dstHost = hostService.getHost(dst);
-
-        Set<ConnectPoint> ingressPoints = getHostLocations(src);
-
-        // FIXME: clearly, this is not enough
-        TrafficSelector selector = DefaultTrafficSelector.builder()
-                .matchEthDst(dstHost.mac()).build();
-        TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment();
-
-        MultiPointToSinglePointIntent intent =
-                MultiPointToSinglePointIntent.builder()
-                        .appId(appId)
-                        .selector(selector)
-                        .treatment(treatment)
-                        .ingressPoints(ingressPoints)
-                        .egressPoint(dstHost.location())
-                        .build();
-
-        intentService.submit(intent);
-        startMonitoringIntent(event, intent);
-    }
-
-
-    private synchronized void startMonitoringIntent(ObjectNode event, Intent intent) {
+    private synchronized void startMonitoringIntent(Intent intent) {
         selectedHosts = new HashSet<>();
         selectedDevices = new HashSet<>();
         selectedIntents = new ArrayList<>();
         selectedIntents.add(intent);
         currentIntentIndex = -1;
-        requestAnotherRelatedIntent(event, +1);
-        requestSelectedIntentTraffic(event);
+        requestAnotherRelatedIntent(+1);
+        requestSelectedIntentTraffic();
     }
 
 
@@ -392,31 +586,27 @@
     }
 
 
-    private synchronized long startTrafficMonitoring(ObjectNode event) {
+    private synchronized void startTrafficMonitoring() {
         stopTrafficMonitoring();
-        trafficEvent = event;
         trafficTask = new TrafficMonitor();
         timer.schedule(trafficTask, TRAFFIC_FREQUENCY, TRAFFIC_FREQUENCY);
-        return number(event, "sid");
     }
 
     private synchronized void stopTrafficMonitoring() {
         if (trafficTask != null) {
             trafficTask.cancel();
             trafficTask = null;
-            trafficEvent = null;
         }
     }
 
     // Subscribes for host traffic messages.
-    private synchronized void requestAllTraffic(ObjectNode event) {
-        long sid = startTrafficMonitoring(event);
-        sendMessage(trafficSummaryMessage(sid));
+    private synchronized void requestAllTraffic() {
+        startTrafficMonitoring();
+        sendMessage(trafficSummaryMessage());
     }
 
-    private void requestDeviceLinkFlows(ObjectNode event) {
-        ObjectNode payload = payload(event);
-        long sid = startTrafficMonitoring(event);
+    private void requestDeviceLinkFlows(ObjectNode payload) {
+        startTrafficMonitoring();
 
         // Get the set of selected hosts and their intents.
         ArrayNode ids = (ArrayNode) payload.path("ids");
@@ -424,47 +614,14 @@
         Set<Device> devices = getDevices(ids);
 
         // If there is a hover node, include it in the hosts and find intents.
-        String hover = string(payload, "hover");
+        String hover = JsonUtils.string(payload, "hover");
         if (!isNullOrEmpty(hover)) {
             addHover(hosts, devices, hover);
         }
-        sendMessage(flowSummaryMessage(sid, devices));
+        sendMessage(flowSummaryMessage(devices));
     }
 
 
-    // Requests related intents message.
-    private synchronized void requestRelatedIntents(ObjectNode event) {
-        ObjectNode payload = payload(event);
-        if (!payload.has("ids")) {
-            return;
-        }
-
-        long sid = number(event, "sid");
-
-        // Cancel any other traffic monitoring mode.
-        stopTrafficMonitoring();
-
-        // Get the set of selected hosts and their intents.
-        ArrayNode ids = (ArrayNode) payload.path("ids");
-        selectedHosts = getHosts(ids);
-        selectedDevices = getDevices(ids);
-        selectedIntents = intentFilter.findPathIntents(selectedHosts, selectedDevices,
-                                                       intentService.getIntents());
-        currentIntentIndex = -1;
-
-        if (haveSelectedIntents()) {
-            // Send a message to highlight all links of all monitored intents.
-            sendMessage(trafficMessage(sid, new TrafficClass("primary", selectedIntents)));
-        }
-
-        // FIXME: Re-introduce one the client click vs hover gesture stuff is sorted out.
-//        String hover = string(payload, "hover");
-//        if (!isNullOrEmpty(hover)) {
-//            // If there is a hover node, include it in the selection and find intents.
-//            processHoverExtendedSelection(sid, hover);
-//        }
-    }
-
     private boolean haveSelectedIntents() {
         return selectedIntents != null && !selectedIntents.isEmpty();
     }
@@ -483,12 +640,12 @@
         secondary.removeAll(primary);
 
         // Send a message to highlight all links of all monitored intents.
-        sendMessage(trafficMessage(sid, new TrafficClass("primary", primary),
+        sendMessage(trafficMessage(new TrafficClass("primary", primary),
                                    new TrafficClass("secondary", secondary)));
     }
 
     // Requests next or previous related intent.
-    private void requestAnotherRelatedIntent(ObjectNode event, int offset) {
+    private void requestAnotherRelatedIntent(int offset) {
         if (haveSelectedIntents()) {
             currentIntentIndex = currentIntentIndex + offset;
             if (currentIntentIndex < 0) {
@@ -496,13 +653,13 @@
             } else if (currentIntentIndex >= selectedIntents.size()) {
                 currentIntentIndex = 0;
             }
-            sendSelectedIntent(event);
+            sendSelectedIntent();
         }
     }
 
     // Sends traffic information on the related intents with the currently
     // selected intent highlighted.
-    private void sendSelectedIntent(ObjectNode event) {
+    private void sendSelectedIntent() {
         Intent selectedIntent = selectedIntents.get(currentIntentIndex);
         log.info("Requested next intent {}", selectedIntent.id());
 
@@ -513,13 +670,12 @@
         secondary.remove(selectedIntent);
 
         // Send a message to highlight all links of the selected intent.
-        sendMessage(trafficMessage(number(event, "sid"),
-                                   new TrafficClass("primary", primary),
+        sendMessage(trafficMessage(new TrafficClass("primary", primary),
                                    new TrafficClass("secondary", secondary)));
     }
 
     // Requests monitoring of traffic for the selected intent.
-    private void requestSelectedIntentTraffic(ObjectNode event) {
+    private void requestSelectedIntentTraffic() {
         if (haveSelectedIntents()) {
             if (currentIntentIndex < 0) {
                 currentIntentIndex = 0;
@@ -531,61 +687,23 @@
             primary.add(selectedIntent);
 
             // Send a message to highlight all links of the selected intent.
-            sendMessage(trafficMessage(number(event, "sid"),
-                                       new TrafficClass("primary", primary, true)));
+            sendMessage(trafficMessage(new TrafficClass("primary", primary, true)));
         }
     }
 
-    // Cancels sending traffic messages.
-    private void cancelTraffic(ObjectNode event) {
-        selectedIntents = null;
-        sendMessage(trafficMessage(number(event, "sid")));
-        stopTrafficMonitoring();
-    }
-
-
-    private synchronized long startSummaryMonitoring(ObjectNode event) {
+    private synchronized void startSummaryMonitoring() {
         stopSummaryMonitoring();
-        summaryEvent = event;
         summaryTask = new SummaryMonitor();
         timer.schedule(summaryTask, SUMMARY_FREQUENCY, SUMMARY_FREQUENCY);
-        return number(event, "sid");
+        summaryRunning = true;
     }
 
     private synchronized void stopSummaryMonitoring() {
-        if (summaryEvent != null) {
+        if (summaryTask != null) {
             summaryTask.cancel();
             summaryTask = null;
-            summaryEvent = null;
         }
-    }
-
-    // Subscribes for summary messages.
-    private synchronized void requestSummary(ObjectNode event) {
-        sendMessage(summmaryMessage(number(event, "sid")));
-    }
-
-
-    // Forces mastership role rebalancing.
-    private void equalizeMasters(ObjectNode event) {
-        directory.get(MastershipAdminService.class).balanceRoles();
-    }
-
-    // Sends a list of sprite names.
-    private void sendSpriteList(ObjectNode event) {
-        ObjectNode root = mapper.createObjectNode();
-        ArrayNode names = mapper.createArrayNode();
-        get(SpriteService.class).getNames().forEach(names::add);
-        root.set("names", names);
-        sendMessage(envelope("spriteListResponse", number(event, "sid"), root));
-    }
-
-    // Sends requested sprite data.
-    private void sendSpriteData(ObjectNode event) {
-        String name = event.path("payload").path("name").asText();
-        ObjectNode root = mapper.createObjectNode();
-        root.set("data", get(SpriteService.class).get(name));
-        sendMessage(envelope("spriteDataResponse", number(event, "sid"), root));
+        summaryRunning = false;
     }
 
 
@@ -666,8 +784,8 @@
     private class InternalIntentListener implements IntentListener {
         @Override
         public void event(IntentEvent event) {
-            if (trafficEvent != null) {
-                requestSelectedIntentTraffic(trafficEvent);
+            if (trafficTask != null) {
+                requestSelectedIntentTraffic();
             }
             eventAccummulator.add(event);
         }
@@ -681,19 +799,38 @@
         }
     }
 
+    // encapsulate
+    private static class TrafficEvent {
+        enum Type { ALL_TRAFFIC, DEV_LINK_FLOWS, SEL_INTENT }
+
+        private final Type type;
+        private final ObjectNode payload;
+
+        TrafficEvent(Type type, ObjectNode payload) {
+            this.type = type;
+            this.payload = payload;
+        }
+    }
+
     // Periodic update of the traffic information
     private class TrafficMonitor extends TimerTask {
         @Override
         public void run() {
             try {
                 if (trafficEvent != null) {
-                    String type = string(trafficEvent, "event", "unknown");
-                    if (type.equals("requestAllTraffic")) {
-                        requestAllTraffic(trafficEvent);
-                    } else if (type.equals("requestDeviceLinkFlows")) {
-                        requestDeviceLinkFlows(trafficEvent);
-                    } else if (type.equals("requestSelectedIntentTraffic")) {
-                        requestSelectedIntentTraffic(trafficEvent);
+                    switch (trafficEvent.type) {
+                        case ALL_TRAFFIC:
+                            requestAllTraffic();
+                            break;
+                        case DEV_LINK_FLOWS:
+                            requestDeviceLinkFlows(trafficEvent.payload);
+                            break;
+                        case SEL_INTENT:
+                            requestSelectedIntentTraffic();
+                            break;
+                        default:
+                            // nothing to do
+                            break;
                     }
                 }
             } catch (Exception e) {
@@ -708,8 +845,8 @@
         @Override
         public void run() {
             try {
-                if (summaryEvent != null) {
-                    requestSummary(summaryEvent);
+                if (summaryRunning) {
+                    requestSummary(0);
                 }
             } catch (Exception e) {
                 log.warn("Unable to handle summary request due to {}", e.getMessage());
@@ -727,8 +864,8 @@
         @Override
         public void processItems(List<Event> items) {
             try {
-                if (summaryEvent != null) {
-                    sendMessage(summmaryMessage(0));
+                if (summaryRunning) {
+                    requestSummary(0);
                 }
             } catch (Exception e) {
                 log.warn("Unable to handle summary request due to {}", e.getMessage());
@@ -737,4 +874,3 @@
         }
     }
 }
-
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 9bfc455..e0a9164 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -63,8 +63,9 @@
 import org.onosproject.net.statistic.StatisticService;
 import org.onosproject.net.topology.Topology;
 import org.onosproject.net.topology.TopologyService;
+import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.UiConnection;
-import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -100,11 +101,13 @@
 /**
  * Facility for creating messages bound for the topology viewer.
  */
-public abstract class TopologyViewMessageHandlerBase extends UiMessageHandler {
+public abstract class TopologyViewMessageHandlerBase extends UiMessageHandlerTwo {
 
-    protected static final Logger log = LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
+    protected static final Logger log =
+            LoggerFactory.getLogger(TopologyViewMessageHandlerBase.class);
 
-    private static final ProviderId PID = new ProviderId("core", "org.onosproject.core", true);
+    private static final ProviderId PID =
+            new ProviderId("core", "org.onosproject.core", true);
     private static final String COMPACT = "%s/%s-%s/%s";
 
     private static final double KB = 1024;
@@ -133,15 +136,6 @@
     private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
 
     /**
-     * Creates a new message handler for the specified set of message types.
-     *
-     * @param messageTypes set of message types
-     */
-    protected TopologyViewMessageHandlerBase(Set<String> messageTypes) {
-        super(messageTypes);
-    }
-
-    /**
      * Returns read-only view of the meta-ui information.
      *
      * @return map of id to meta-ui mementos
@@ -168,26 +162,6 @@
         version = ver.replace(".SNAPSHOT", "*").replaceFirst("~.*$", "");
     }
 
-    // Retrieves the payload from the specified event.
-    protected ObjectNode payload(ObjectNode event) {
-        return (ObjectNode) event.path("payload");
-    }
-
-    // Returns the specified node property as a number
-    protected long number(ObjectNode node, String name) {
-        return node.path(name).asLong();
-    }
-
-    // Returns the specified node property as a string.
-    protected String string(ObjectNode node, String name) {
-        return node.path(name).asText();
-    }
-
-    // Returns the specified node property as a string.
-    protected String string(ObjectNode node, String name, String defaultValue) {
-        return node.path(name).asText(defaultValue);
-    }
-
     // Returns the specified set of IP addresses as a string.
     private String ip(Set<IpAddress> ipAddresses) {
         Iterator<IpAddress> it = ipAddresses.iterator();
@@ -222,21 +196,11 @@
 
     // Produces a log message event bound to the client.
     private ObjectNode message(String severity, long id, String message) {
-        return envelope("message", id,
-                        mapper.createObjectNode()
-                                .put("severity", severity)
-                                .put("message", message));
-    }
+        ObjectNode payload = mapper.createObjectNode()
+                .put("severity", severity)
+                .put("message", message);
 
-    // Puts the payload into an envelope and returns it.
-    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("message", id, payload);
     }
 
     // Produces a set of all hosts listed in the specified JSON array.
@@ -320,7 +284,7 @@
                 ((event.type() == INSTANCE_ADDED) ? "addInstance" :
                         ((event.type() == INSTANCE_REMOVED ? "removeInstance" :
                                 "addInstance")));
-        return envelope(type, 0, payload);
+        return JsonUtils.envelope(type, 0, payload);
     }
 
     // Produces a device event message to the client.
@@ -347,7 +311,7 @@
 
         String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
                 ((event.type() == DEVICE_REMOVED) ? "removeDevice" : "updateDevice");
-        return envelope(type, 0, payload);
+        return JsonUtils.envelope(type, 0, payload);
     }
 
     // Produces a link event message to the client.
@@ -364,7 +328,7 @@
                 .put("dstPort", link.dst().port().toString());
         String type = (event.type() == LINK_ADDED) ? "addLink" :
                 ((event.type() == LINK_REMOVED) ? "removeLink" : "updateLink");
-        return envelope(type, 0, payload);
+        return JsonUtils.envelope(type, 0, payload);
     }
 
     // Produces a host event message to the client.
@@ -385,7 +349,7 @@
 
         String type = (event.type() == HOST_ADDED) ? "addHost" :
                 ((event.type() == HOST_REMOVED) ? "removeHost" : "updateHost");
-        return envelope(type, 0, payload);
+        return JsonUtils.envelope(type, 0, payload);
     }
 
     // Encodes the specified host location into a JSON object.
@@ -447,15 +411,15 @@
     }
 
     // Updates meta UI information for the specified object.
-    protected void updateMetaUi(ObjectNode event) {
-        ObjectNode payload = payload(event);
-        metaUi.put(string(payload, "id"), (ObjectNode) payload.path("memento"));
+    protected void updateMetaUi(ObjectNode payload) {
+        metaUi.put(JsonUtils.string(payload, "id"),
+                   JsonUtils.node(payload, "memento"));
     }
 
     // Returns summary response.
     protected ObjectNode summmaryMessage(long sid) {
         Topology topology = topologyService.currentTopology();
-        return envelope("showSummary", sid,
+        return JsonUtils.envelope("showSummary", sid,
                         json("ONOS Summary", "node",
                              new Prop("Devices", format(topology.deviceCount())),
                              new Prop("Links", format(topology.linkCount())),
@@ -474,7 +438,7 @@
         String name = annot.value(AnnotationKeys.NAME);
         int portCount = deviceService.getPorts(deviceId).size();
         int flowCount = getFlowCount(deviceId);
-        return envelope("showDetails", sid,
+        return JsonUtils.envelope("showDetails", sid,
                         json(isNullOrEmpty(name) ? deviceId.toString() : name,
                              device.type().toString().toLowerCase(),
                              new Prop("URI", deviceId.toString()),
@@ -552,7 +516,7 @@
         String type = annot.value(AnnotationKeys.TYPE);
         String name = annot.value(AnnotationKeys.NAME);
         String vlan = host.vlan().toString();
-        return envelope("showDetails", sid,
+        return JsonUtils.envelope("showDetails", sid,
                         json(isNullOrEmpty(name) ? hostId.toString() : name,
                              isNullOrEmpty(type) ? "endstation" : type,
                              new Prop("MAC", host.mac().toString()),
@@ -565,7 +529,7 @@
 
 
     // Produces JSON message to trigger traffic overview visualization
-    protected ObjectNode trafficSummaryMessage(long sid) {
+    protected ObjectNode trafficSummaryMessage() {
         ObjectNode payload = mapper.createObjectNode();
         ArrayNode paths = mapper.createArrayNode();
         payload.set("paths", paths);
@@ -603,7 +567,7 @@
                 }
             }
         }
-        return envelope("showTraffic", sid, payload);
+        return JsonUtils.envelope("showTraffic", 0, payload);
     }
 
     private Collection<BiLink> consolidateLinks(Iterable<Link> links) {
@@ -615,7 +579,7 @@
     }
 
     // Produces JSON message to trigger flow overview visualization
-    protected ObjectNode flowSummaryMessage(long sid, Set<Device> devices) {
+    protected ObjectNode flowSummaryMessage(Set<Device> devices) {
         ObjectNode payload = mapper.createObjectNode();
         ArrayNode paths = mapper.createArrayNode();
         payload.set("paths", paths);
@@ -626,7 +590,7 @@
                 addLinkFlows(link, paths, counts.get(link));
             }
         }
-        return envelope("showTraffic", sid, payload);
+        return JsonUtils.envelope("showTraffic", 0, payload);
     }
 
     private void addLinkFlows(Link link, ArrayNode paths, Integer count) {
@@ -644,7 +608,7 @@
 
 
     // Produces JSON message to trigger traffic visualization
-    protected ObjectNode trafficMessage(long sid, TrafficClass... trafficClasses) {
+    protected ObjectNode trafficMessage(TrafficClass... trafficClasses) {
         ObjectNode payload = mapper.createObjectNode();
         ArrayNode paths = mapper.createArrayNode();
         payload.set("paths", paths);
@@ -670,7 +634,7 @@
             ((ArrayNode) pathNode.path("labels")).add(hasTraffic ? formatBytes(biLink.bytes) : "");
         }
 
-        return envelope("showTraffic", sid, payload);
+        return JsonUtils.envelope("showTraffic", 0, payload);
     }
 
     // Classifies the link traffic according to the specified classes.
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
index 1fbbddf..d756da0 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
@@ -24,8 +24,8 @@
 import org.onosproject.cluster.ControllerNode;
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiExtensionService;
-import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiMessageHandlerTwo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -56,7 +56,7 @@
 
     private long lastActive = System.currentTimeMillis();
 
-    private Map<String, UiMessageHandler> handlers;
+    private Map<String, UiMessageHandlerTwo> handlers;
 
     /**
      * Creates a new web-socket for serving data to GUI.
@@ -123,7 +123,7 @@
         try {
             ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
             String type = message.path("event").asText("unknown");
-            UiMessageHandler handler = handlers.get(type);
+            UiMessageHandlerTwo handler = handlers.get(type);
             if (handler != null) {
                 handler.process(message);
             } else {