ONOS-1479 - GUI Topology Overlay Work - (WIP)
- UiExtension now uses Builder Pattern; added topology overlay factory.
- Refactored UiExtensionTest (and other classes) to use builder.
- Created UiTopoOverlayFactory, UiTopoOverlay, and TopoOverlayCache.
- Started implementation of TrafficOverlay.
- Inject TopoOverlayCache into TopologyViewMessageHandler; added TopoSelectOverlay request handler.
- Modified UiExtensionManager to create traffic overlay.
- Augmented UiWebSocket to create overlays on demand, and inject overlay cache into topo view message handler.
- added client side wiring to switch overlays.

Change-Id: I6f99596aefb3b87382517ce929d268a2447545ee
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 d9c9dad..a44ead5 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
@@ -62,7 +62,9 @@
     );
 
     private UiExtension uiExtension =
-            new UiExtension(views, this::newHandlers, getClass().getClassLoader());
+            new UiExtension.Builder(getClass().getClassLoader(), views)
+                .messageHandlerFactory(this::newHandlers)
+                .build();
 
     private IntentPerfCollector collector;
 
diff --git a/core/api/src/main/java/org/onosproject/ui/UiExtension.java b/core/api/src/main/java/org/onosproject/ui/UiExtension.java
index cc10506..c310858 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiExtension.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiExtension.java
@@ -15,61 +15,47 @@
  */
 package org.onosproject.ui;
 
-import com.google.common.collect.ImmutableList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.List;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 /**
  * User interface extension.
  */
-public class UiExtension {
+public final class UiExtension {
 
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     private static final String VIEW_PREFIX = "app/view/";
+    private static final String EMPTY = "";
+    private static final String SLASH = "/";
+    private static final String CSS_HTML = "css.html";
+    private static final String JS_HTML = "js.html";
 
-    private final String prefix;
     private final ClassLoader classLoader;
+    private final String resourcePath;
     private final List<UiView> views;
     private final UiMessageHandlerFactory messageHandlerFactory;
+    private final UiTopoOverlayFactory topoOverlayFactory;
 
-    /**
-     * Creates a user interface extension for loading CSS and JS injections
-     * from {@code css.html} and {@code js.html} resources, respectively.
-     *
-     * @param views                 list of contributed views
-     * @param messageHandlerFactory optional message handler factory
-     * @param classLoader           class-loader for user interface resources
-     */
-    public UiExtension(List<UiView> views,
-                       UiMessageHandlerFactory messageHandlerFactory,
-                       ClassLoader classLoader) {
-        this(views, messageHandlerFactory, null, classLoader);
+
+    // private constructor - only the builder calls this
+    private UiExtension(ClassLoader cl, String path, List<UiView> views,
+                        UiMessageHandlerFactory mhFactory,
+                        UiTopoOverlayFactory toFactory) {
+        this.classLoader = cl;
+        this.resourcePath = path;
+        this.views = views;
+        this.messageHandlerFactory = mhFactory;
+        this.topoOverlayFactory = toFactory;
     }
 
-    /**
-     * Creates a user interface extension using custom resource prefix. It
-     * loads CSS and JS injections from {@code path/css.html} and
-     * {@code prefix/js.html} resources, respectively.
-     *
-     * @param views                 list of user interface views
-     * @param messageHandlerFactory optional message handler factory
-     * @param path                  resource path prefix
-     * @param classLoader           class-loader for user interface resources
-     */
-    public UiExtension(List<UiView> views,
-                       UiMessageHandlerFactory messageHandlerFactory,
-                       String path, ClassLoader classLoader) {
-        this.views = checkNotNull(ImmutableList.copyOf(views), "Views cannot be null");
-        this.messageHandlerFactory = messageHandlerFactory;
-        this.prefix = path != null ? (path + "/") : "";
-        this.classLoader = checkNotNull(classLoader, "Class loader must be specified");
-    }
 
     /**
      * Returns input stream containing CSS inclusion statements.
@@ -77,7 +63,7 @@
      * @return CSS inclusion statements
      */
     public InputStream css() {
-        return getStream(prefix + "css.html");
+        return getStream(resourcePath + CSS_HTML);
     }
 
     /**
@@ -86,7 +72,7 @@
      * @return JavaScript inclusion statements
      */
     public InputStream js() {
-       return getStream(prefix + "js.html");
+       return getStream(resourcePath + JS_HTML);
     }
 
     /**
@@ -106,18 +92,28 @@
      * @return resource input stream
      */
     public InputStream resource(String viewId, String path) {
-        return getStream(VIEW_PREFIX + viewId + "/" + path);
+        return getStream(VIEW_PREFIX + viewId + SLASH + path);
     }
 
     /**
-     * Returns message handler factory.
+     * Returns message handler factory, if one was defined.
      *
-     * @return message handlers
+     * @return message handler factory
      */
     public UiMessageHandlerFactory messageHandlerFactory() {
         return messageHandlerFactory;
     }
 
+    /**
+     * Returns the topology overlay factory, if one was defined.
+     *
+     * @return topology overlay factory
+     */
+    public UiTopoOverlayFactory topoOverlayFactory() {
+        return topoOverlayFactory;
+    }
+
+
     // Returns the resource input stream from the specified class-loader.
     private InputStream getStream(String path) {
         InputStream stream = classLoader.getResourceAsStream(path);
@@ -128,4 +124,76 @@
     }
 
 
+    /**
+     * UI Extension Builder.
+     */
+    public static class Builder {
+        private ClassLoader classLoader;
+
+        private String resourcePath = EMPTY;
+        private List<UiView> views = new ArrayList<>();
+        private UiMessageHandlerFactory messageHandlerFactory = null;
+        private UiTopoOverlayFactory topoOverlayFactory = null;
+
+        /**
+         * Create a builder with the given class loader.
+         * Resource path defaults to "".
+         * Views defaults to an empty list.
+         * Both Message and TopoOverlay factories default to null.
+         *
+         * @param cl the classloader
+         */
+        public Builder(ClassLoader cl, List<UiView> views) {
+            checkNotNull(cl, "Must provide a class loader");
+            checkArgument(views.size() > 0, "Must provide at least one view");
+            this.classLoader = cl;
+            this.views = views;
+        }
+
+        /**
+         * Set the resource path. That is, path to where the CSS and JS
+         * files are located. This value should
+         *
+         * @param path resource path
+         * @return self, for chaining
+         */
+        public Builder resourcePath(String path) {
+            this.resourcePath = path == null ? EMPTY : path + SLASH;
+            return this;
+        }
+
+        /**
+         * Sets the message handler factory for this extension.
+         *
+         * @param mhFactory message handler factory
+         * @return self, for chaining
+         */
+        public Builder messageHandlerFactory(UiMessageHandlerFactory mhFactory) {
+            this.messageHandlerFactory = mhFactory;
+            return this;
+        }
+
+        /**
+         * Sets the topology overlay factory for this extension.
+         *
+         * @param toFactory topology overlay factory
+         * @return self, for chaining
+         */
+        public Builder topoOverlayFactory(UiTopoOverlayFactory toFactory) {
+            this.topoOverlayFactory = toFactory;
+            return this;
+        }
+
+        /**
+         * Builds the UI extension.
+         *
+         * @return UI extension instance
+         */
+        public UiExtension build() {
+            return new UiExtension(classLoader, resourcePath, views,
+                                   messageHandlerFactory, topoOverlayFactory);
+        }
+
+    }
+
 }
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 b3782a2..e27f5d9 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiMessageHandler.java
@@ -107,8 +107,9 @@
     void exec(String eventType, long sid, ObjectNode payload) {
         RequestHandler requestHandler = handlerMap.get(eventType);
         if (requestHandler != null) {
-            log.debug("process {} event...", eventType);
             requestHandler.process(sid, payload);
+        } else {
+            log.warn("no request handler for event type {}", eventType);
         }
     }
 
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java b/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
new file mode 100644
index 0000000..999067a
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
@@ -0,0 +1,78 @@
+/*
+ * 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Represents user interface topology view overlay.
+ */
+public class UiTopoOverlay {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final String id;
+
+    /**
+     * Creates a new user interface topology view overlay descriptor.
+     *
+     * @param id overlay identifier
+     */
+    public UiTopoOverlay(String id) {
+        this.id = id;
+    }
+
+    /**
+     * Returns the identifier for this overlay.
+     *
+     * @return the identifier
+     */
+    public String id() {
+        return id;
+    }
+
+    /**
+     * Callback invoked to initialize this overlay, soon after creation.
+     * This default implementation does nothing.
+     */
+    public void init() {
+    }
+
+    /**
+     * Callback invoked when this overlay is activated.
+     */
+    public void activate() {
+        log.debug("Overlay '{}' Activated", id);
+    }
+
+    /**
+     * Callback invoked when this overlay is deactivated.
+     */
+    public void deactivate() {
+        log.debug("Overlay '{}' Deactivated", id);
+    }
+
+    /**
+     * Callback invoked to destroy this instance by cleaning up any
+     * internal state ready for garbage collection.
+     * This default implementation does nothing.
+     */
+    public void destroy() {
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java b/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java
new file mode 100644
index 0000000..bd2f2fe
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoOverlayFactory.java
@@ -0,0 +1,34 @@
+/*
+ * 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 java.util.Collection;
+
+/**
+ * Abstraction of an entity capable of producing one or more topology
+ * overlay handlers specific to a given user interface connection.
+ */
+public interface UiTopoOverlayFactory {
+
+    /**
+     * Produces a collection of new overlay handlers.
+     *
+     * @return collection of new overlay handlers
+     */
+    Collection<UiTopoOverlay> newOverlays();
+}
diff --git a/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java b/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java
index efc0261..bd3d15f 100644
--- a/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java
+++ b/core/api/src/test/java/org/onosproject/ui/UiExtensionTest.java
@@ -19,6 +19,7 @@
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.List;
 
 import static com.google.common.io.ByteStreams.toByteArray;
 import static org.junit.Assert.*;
@@ -29,29 +30,111 @@
  */
 public class UiExtensionTest {
 
+    private static final String FOO_ID = "foo";
+    private static final String FOO_LABEL = "Foo View";
+    private static final String BAR_ID = "bar";
+    private static final String BAR_LABEL = "Bar View";
+    private static final String CUSTOM = "custom";
+
+
+    private static final UiView FOO_VIEW = new UiView(OTHER, FOO_ID, FOO_LABEL);
+    private static final UiView BAR_VIEW = new UiView(OTHER, BAR_ID, BAR_LABEL);
+    private static final UiView HIDDEN_VIEW = new UiViewHidden(FOO_ID);
+
+    private static final UiMessageHandlerFactory MH_FACTORY = () -> null;
+    private static final UiTopoOverlayFactory TO_FACTORY = () -> null;
+
+    private final ClassLoader cl = getClass().getClassLoader();
+
+    private List<UiView> viewList;
+    private UiExtension ext;
+    private String css;
+    private String js;
+    private UiView view;
+
+
+
     @Test
     public void basics() throws IOException {
-        UiExtension ext = new UiExtension(ImmutableList.of(new UiView(OTHER, "foo", "Foo View")),
-                                          null,
-                                          getClass().getClassLoader());
-        String css = new String(toByteArray(ext.css()));
+        viewList = ImmutableList.of(FOO_VIEW);
+        ext = new UiExtension.Builder(cl, viewList).build();
+
+        css = new String(toByteArray(ext.css()));
         assertTrue("incorrect css stream", css.contains("foo-css"));
-        String js = new String(toByteArray(ext.js()));
+        js = new String(toByteArray(ext.js()));
         assertTrue("incorrect js stream", js.contains("foo-js"));
-        assertEquals("incorrect view id", "foo", ext.views().get(0).id());
-        assertEquals("incorrect view category", OTHER, ext.views().get(0).category());
-        assertNull("incorrect handler factory", ext.messageHandlerFactory());
+
+        assertEquals("expected 1 view", 1, ext.views().size());
+        view = ext.views().get(0);
+        assertEquals("wrong view category", OTHER, view.category());
+        assertEquals("wrong view id", FOO_ID, view.id());
+        assertEquals("wrong view label", FOO_LABEL, view.label());
+
+        assertNull("unexpected message handler factory", ext.messageHandlerFactory());
+        assertNull("unexpected topo overlay factory", ext.topoOverlayFactory());
     }
 
     @Test
     public void withPath() throws IOException {
-        UiExtension ext = new UiExtension(ImmutableList.of(new UiView(OTHER, "foo", "Foo View")),
-                                          null, "custom", getClass().getClassLoader());
-        String css = new String(toByteArray(ext.css()));
+        viewList = ImmutableList.of(FOO_VIEW);
+        ext = new UiExtension.Builder(cl, viewList)
+                .resourcePath(CUSTOM)
+                .build();
+
+        css = new String(toByteArray(ext.css()));
         assertTrue("incorrect css stream", css.contains("custom-css"));
-        String js = new String(toByteArray(ext.js()));
+        js = new String(toByteArray(ext.js()));
         assertTrue("incorrect js stream", js.contains("custom-js"));
-        assertEquals("incorrect view id", "foo", ext.views().get(0).id());
-        assertNull("incorrect handler factory", ext.messageHandlerFactory());
+
+        assertEquals("expected 1 view", 1, ext.views().size());
+        view = ext.views().get(0);
+        assertEquals("wrong view category", OTHER, view.category());
+        assertEquals("wrong view id", FOO_ID, view.id());
+        assertEquals("wrong view label", FOO_LABEL, view.label());
+
+        assertNull("unexpected message handler factory", ext.messageHandlerFactory());
+        assertNull("unexpected topo overlay factory", ext.topoOverlayFactory());
+    }
+
+    @Test
+    public void messageHandlerFactory() {
+        viewList = ImmutableList.of(FOO_VIEW);
+        ext = new UiExtension.Builder(cl, viewList)
+                .messageHandlerFactory(MH_FACTORY)
+                .build();
+
+        assertEquals("wrong message handler factory", MH_FACTORY,
+                     ext.messageHandlerFactory());
+        assertNull("unexpected topo overlay factory", ext.topoOverlayFactory());
+    }
+
+    @Test
+    public void topoOverlayFactory() {
+        viewList = ImmutableList.of(HIDDEN_VIEW);
+        ext = new UiExtension.Builder(cl, viewList)
+                .topoOverlayFactory(TO_FACTORY)
+                .build();
+
+        assertNull("unexpected message handler factory", ext.messageHandlerFactory());
+        assertEquals("wrong topo overlay factory", TO_FACTORY,
+                     ext.topoOverlayFactory());
+    }
+
+    @Test
+    public void twoViews() {
+        viewList = ImmutableList.of(FOO_VIEW, BAR_VIEW);
+        ext = new UiExtension.Builder(cl, viewList).build();
+
+        assertEquals("expected 2 views", 2, ext.views().size());
+
+        view = ext.views().get(0);
+        assertEquals("wrong view category", OTHER, view.category());
+        assertEquals("wrong view id", FOO_ID, view.id());
+        assertEquals("wrong view label", FOO_LABEL, view.label());
+
+        view = ext.views().get(1);
+        assertEquals("wrong view category", OTHER, view.category());
+        assertEquals("wrong view id", BAR_ID, view.id());
+        assertEquals("wrong view label", BAR_LABEL, view.label());
     }
 }
\ No newline at end of file
diff --git a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java
index 1d241091..f40bcb5 100644
--- a/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java
+++ b/tools/package/archetypes/ui/src/main/resources/archetype-resources/src/main/java/AppUiComponent.java
@@ -57,8 +57,9 @@
 
     // Application UI extension
     protected UiExtension extension =
-            new UiExtension(uiViews, messageHandlerFactory,
-                            getClass().getClassLoader());
+            new UiExtension.Builder(getClass().getClassLoader(), uiViews)
+                    .messageHandlerFactory(messageHandlerFactory)
+                    .build();
 
     @Activate
     protected void activate() {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopoOverlayCache.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopoOverlayCache.java
new file mode 100644
index 0000000..2490041
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopoOverlayCache.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onosproject.ui.impl;
+
+import org.onosproject.ui.UiTopoOverlay;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A cache of {@link org.onosproject.ui.UiTopoOverlay}'s that were registered
+ * at the time the UI connection was established.
+ */
+public class TopoOverlayCache {
+
+    private final Map<String, UiTopoOverlay> overlays = new HashMap<>();
+
+    /**
+     * Adds a topology overlay to the cache.
+     *
+     * @param overlay   a topology overlay
+     */
+    public void add(UiTopoOverlay overlay) {
+        overlays.put(overlay.id(), overlay);
+    }
+
+    /**
+     * Invoked when the cache is no longer needed.
+     */
+    public void destroy() {
+        overlays.clear();
+    }
+
+    /**
+     * Switching currently selected overlay.
+     *
+     * @param deact identity of overlay to deactivate
+     * @param act identity of overlay to activate
+     */
+    public void switchOverlay(String deact, String act) {
+        UiTopoOverlay toDeactivate = getOverlay(deact);
+        UiTopoOverlay toActivate = getOverlay(act);
+        if (toDeactivate != null) {
+            toDeactivate.deactivate();
+        }
+        if (toActivate != null) {
+            toActivate.activate();
+        }
+    }
+
+    private UiTopoOverlay getOverlay(String id) {
+        return id == null ? null : overlays.get(id);
+    }
+
+    /**
+     * Returns the number of overlays in the cache.
+     *
+     * @return number of overlays
+     */
+    public int size() {
+        return overlays.size();
+    }
+}
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 a10e016..6d2295a 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
@@ -103,6 +103,7 @@
     private static final String SPRITE_DATA_REQ = "spriteDataRequest";
     private static final String TOPO_START = "topoStart";
     private static final String TOPO_HEARTBEAT = "topoHeartbeat";
+    private static final String TOPO_SELECT_OVERLAY = "topoSelectOverlay";
     private static final String TOPO_STOP = "topoStop";
 
 
@@ -135,6 +136,8 @@
     private final ExecutorService msgSender =
             newSingleThreadExecutor(groupedThreads("onos/gui", "msg-sender"));
 
+    private TopoOverlayCache overlayCache;
+
     private TimerTask trafficTask = null;
     private TrafficEvent trafficEvent = null;
 
@@ -172,6 +175,7 @@
         return ImmutableSet.of(
                 new TopoStart(),
                 new TopoHeartbeat(),
+                new TopoSelectOverlay(),
                 new TopoStop(),
                 new ReqSummary(),
                 new CancelSummary(),
@@ -195,6 +199,15 @@
         );
     }
 
+    /**
+     * Injects the topology overlay cache.
+     *
+     * @param overlayCache injected cache
+     */
+    void setOverlayCache(TopoOverlayCache overlayCache) {
+        this.overlayCache = overlayCache;
+    }
+
     // ==================================================================
 
     /**
@@ -231,6 +244,19 @@
         }
     }
 
+    private final class TopoSelectOverlay extends RequestHandler {
+        private TopoSelectOverlay() {
+            super(TOPO_SELECT_OVERLAY);
+        }
+
+        @Override
+        public void process(long sid, ObjectNode payload) {
+            String deact = string(payload, "deactivate");
+            String act = string(payload, "activate");
+            overlayCache.switchOverlay(deact, act);
+        }
+    }
+
     /**
      * @deprecated in Cardinal Release
      */
@@ -311,7 +337,7 @@
         @Override
         public void process(long sid, ObjectNode payload) {
             String type = string(payload, "class", "unknown");
-            String id = JsonUtils.string(payload, "id");
+            String id = string(payload, "id");
 
             if (type.equals("device")) {
                 sendMessage(deviceDetails(deviceId(id), sid));
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TrafficOverlay.java b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficOverlay.java
new file mode 100644
index 0000000..3c09bb4
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TrafficOverlay.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.onosproject.ui.impl;
+
+import org.onosproject.ui.UiTopoOverlay;
+
+/**
+ * Topology Overlay for network traffic.
+ */
+public class TrafficOverlay extends UiTopoOverlay {
+    private static final String TRAFFIC_ID = "traffic";
+
+    /**
+     * Constructs the traffic overlay.
+     */
+    public TrafficOverlay() {
+        super(TRAFFIC_ID);
+    }
+
+    // TODO : override init(), activate(), deactivate(), destroy()
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 5706734..c3c9538 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
@@ -30,6 +30,7 @@
 import org.onosproject.ui.UiExtension;
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiTopoOverlayFactory;
 import org.onosproject.ui.UiView;
 import org.onosproject.ui.UiViewHidden;
 import org.onosproject.ui.impl.topo.OverlayService;
@@ -54,6 +55,10 @@
 public class UiExtensionManager
         implements UiExtensionService, SpriteService, OverlayService {
 
+    private static final ClassLoader CL =
+            UiExtensionManager.class.getClassLoader();
+    private static final String CORE = "core";
+
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     // List of all extensions
@@ -104,8 +109,16 @@
                         new ClusterViewMessageHandler()
                 );
 
-        return new UiExtension(coreViews, messageHandlerFactory, "core",
-                               UiExtensionManager.class.getClassLoader());
+        UiTopoOverlayFactory topoOverlayFactory =
+                () -> ImmutableList.of(
+                        new TrafficOverlay()
+                );
+
+        return new UiExtension.Builder(CL, coreViews)
+                .messageHandlerFactory(messageHandlerFactory)
+                .topoOverlayFactory(topoOverlayFactory)
+                .resourcePath(CORE)
+                .build();
     }
 
     @Activate
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 b80c11f..761c2a3 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
@@ -27,6 +27,7 @@
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandlerFactory;
 import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.UiTopoOverlayFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -58,6 +59,7 @@
     private long lastActive = System.currentTimeMillis();
 
     private Map<String, UiMessageHandler> handlers;
+    private TopoOverlayCache overlayCache;
 
     /**
      * Creates a new web-socket for serving data to GUI.
@@ -72,7 +74,7 @@
      * Issues a close on the connection.
      */
     synchronized void close() {
-        destroyHandlers();
+        destroyHandlersAndOverlays();
         if (connection.isOpen()) {
             connection.close();
         }
@@ -104,7 +106,7 @@
         this.connection = connection;
         this.control = (FrameConnection) connection;
         try {
-            createHandlers();
+            createHandlersAndOverlays();
             sendInstanceData();
             log.info("GUI client connected");
 
@@ -118,7 +120,7 @@
 
     @Override
     public synchronized void onClose(int closeCode, String message) {
-        destroyHandlers();
+        destroyHandlersAndOverlays();
         log.info("GUI client disconnected [close-code={}, message={}]",
                  closeCode, message);
     }
@@ -131,6 +133,7 @@
 
     @Override
     public void onMessage(String data) {
+        log.debug("onMessage: {}", data);
         lastActive = System.currentTimeMillis();
         try {
             ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
@@ -172,8 +175,11 @@
     }
 
     // Creates new message handlers.
-    private synchronized void createHandlers() {
+    private synchronized void createHandlersAndOverlays() {
+        log.debug("creating handlers and overlays...");
         handlers = new HashMap<>();
+        overlayCache = new TopoOverlayCache();
+
         UiExtensionService service = directory.get(UiExtensionService.class);
         service.getExtensions().forEach(ext -> {
             UiMessageHandlerFactory factory = ext.messageHandlerFactory();
@@ -181,15 +187,33 @@
                 factory.newHandlers().forEach(handler -> {
                     handler.init(this, directory);
                     handler.messageTypes().forEach(type -> handlers.put(type, handler));
+
+                    // need to inject the overlay cache into topology message handler
+                    if (handler instanceof TopologyViewMessageHandler) {
+                        ((TopologyViewMessageHandler) handler).setOverlayCache(overlayCache);
+                    }
                 });
             }
+
+            UiTopoOverlayFactory overlayFactory = ext.topoOverlayFactory();
+            if (overlayFactory != null) {
+                overlayFactory.newOverlays().forEach(overlayCache::add);
+            }
         });
+        log.debug("#handlers = {}, #overlays = {}", handlers.size(),
+                  overlayCache.size());
     }
 
     // Destroys message handlers.
-    private synchronized void destroyHandlers() {
+    private synchronized void destroyHandlersAndOverlays() {
+        log.debug("destroying handlers and overlays...");
         handlers.forEach((type, handler) -> handler.destroy());
         handlers.clear();
+
+        if (overlayCache != null) {
+            overlayCache.destroy();
+            overlayCache = null;
+        }
     }
 
     // Sends cluster node/instance information to allow GUI to fail-over.
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
index e1efe47..fd704a6 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -30,10 +30,11 @@
     var tos = 'TopoOverlayService: ';
 
     // injected refs
-    var $log, fs, gs;
+    var $log, fs, gs, wss;
 
     // internal state
-    var overlays = {};
+    var overlays = {},
+        current = null;
 
     function error(fn, msg) {
         $log.error(tos + fn + '(): ' + msg);
@@ -108,20 +109,42 @@
         return overlays[id];
     }
 
+    // an overlay was selected via toolbar radio button press from user
+    function tbSelection(id) {
+        var same = current && current.overlayId === id,
+            payload = {};
+
+        function doop(op) {
+            var oid = current.overlayId;
+            $log.debug('Overlay:', op, oid);
+            current[op]();
+            payload[op] = oid;
+        }
+
+        if (!same) {
+            current && doop('deactivate');
+            current = overlay(id);
+            current && doop('activate');
+            wss.sendEvent('topoSelectOverlay', payload);
+        }
+    }
+
     angular.module('ovTopo')
     .factory('TopoOverlayService',
-        ['$log', 'FnService', 'GlyphService',
+        ['$log', 'FnService', 'GlyphService', 'WebSocketService',
 
-        function (_$log_, _fs_, _gs_) {
+        function (_$log_, _fs_, _gs_, _wss_) {
             $log = _$log_;
             fs = _fs_;
             gs = _gs_;
+            wss = _wss_;
 
             return {
                 register: register,
                 unregister: unregister,
                 list: list,
-                overlay: overlay
+                overlay: overlay,
+                tbSelection: tbSelection
             }
         }]);
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
index 342e108..cbf443a 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoToolbar.js
@@ -50,14 +50,14 @@
         L: { id: 'cycleLabels-btn', gid: 'cycleLabels' },
         R: { id: 'resetZoom-btn', gid: 'resetZoom' },
 
+        E: { id: 'eqMaster-btn', gid: 'eqMaster' },
+
         V: { id: 'relatedIntents-btn', gid: 'relatedIntents' },
         leftArrow: { id: 'prevIntent-btn', gid: 'prevIntent' },
         rightArrow: { id: 'nextIntent-btn', gid: 'nextIntent' },
         W: { id: 'intentTraffic-btn', gid: 'intentTraffic' },
         A: { id: 'allTraffic-btn', gid: 'allTraffic' },
-        F: { id: 'flows-btn', gid: 'flows' },
-
-        E: { id: 'eqMaster-btn', gid: 'eqMaster' }
+        F: { id: 'flows-btn', gid: 'flows' }
     };
 
     // initial toggle state: default settings and tag to key mapping
@@ -153,14 +153,6 @@
         addButton('E');
     }
 
-    function setOverlay(overlayId) {
-        if (!overlayId) {
-            $log.debug('CLEAR overlay');
-        } else {
-            $log.debug('SET overlay', overlayId);
-        }
-    }
-
     function addOverlays() {
         toolbar.addSeparator();
 
@@ -168,7 +160,9 @@
         var rset = [{
                 gid: 'unknown',
                 tooltip: 'No Overlay',
-                cb: function () { setOverlay(); }
+                cb: function () {
+                    tov.tbSelection(null);
+                }
             }];
 
         tov.list().forEach(function (key) {
@@ -177,7 +171,7 @@
                 gid: ov._glyphId,
                 tooltip: (ov.tooltip || '(no tooltip)'),
                 cb: function () {
-                    setOverlay(ov.overlayId);
+                    tov.tbSelection(ov.overlayId);
                 }
             });
         });
@@ -186,6 +180,7 @@
     }
 
     // TODO: 3rd row needs to be swapped in/out based on selected overlay
+    // NOTE: This particular row of buttons is for the traffic overlay
     function addThirdRow() {
         addButton('V');
         addButton('leftArrow');