ONOS-6258: UiTopo2Overlay et al.
 - initial support for topo-2 highlighting.

Change-Id: I71c61b902047153ea420a8b2ecd89f6950daa4a9
diff --git a/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverViewComponent.java b/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverViewComponent.java
index b8d66de..6d8b214 100644
--- a/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverViewComponent.java
+++ b/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/DriverViewComponent.java
@@ -24,6 +24,7 @@
 import org.onosproject.ui.UiExtension;
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiTopo2OverlayFactory;
 import org.onosproject.ui.UiView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -56,11 +57,22 @@
                     new DriverViewMessageHandler()
             );
 
+    // ++++ ====================================================== ++++
+    // ++++ Temporary code for testing the topology-2 overlay code ++++
+
+    private final UiTopo2OverlayFactory t2ovFactory =
+            () -> ImmutableList.of(
+                    new TesterTopo2Overlay()
+            );
+
+    // ++++ ====================================================== ++++
+
     // Application UI extension
     protected UiExtension extension =
             new UiExtension.Builder(getClass().getClassLoader(), uiViews)
                     .resourcePath(VIEW_ID)
                     .messageHandlerFactory(messageHandlerFactory)
+                    .topo2OverlayFactory(t2ovFactory)   // +++ TEMP +++
                     .build();
 
     @Activate
diff --git a/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/TesterTopo2Overlay.java b/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/TesterTopo2Overlay.java
new file mode 100644
index 0000000..df81adf
--- /dev/null
+++ b/apps/drivermatrix/src/main/java/org/onosproject/drivermatrix/TesterTopo2Overlay.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017-present 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.drivermatrix;
+
+import org.onosproject.ui.UiTopo2Overlay;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A test implementation of UiTopo2Overlay.
+ */
+public class TesterTopo2Overlay extends UiTopo2Overlay {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    // NOTE: this must match the ID defined in dmatrixTopo2v.js
+    private static final String OVERLAY_ID = "dmatrix-test-overlay";
+    private static final String NAME = "Test D-Matrix Overlay";
+
+    /**
+     * Constructs the overlay.
+     */
+    public TesterTopo2Overlay() {
+        super(OVERLAY_ID, NAME);
+        log.debug("+++ CREATE +++ TesterTopo2Overlay");
+    }
+
+    @Override
+    public String glyphId() {
+        return "thatsNoMoon";
+    }
+
+    @Override
+    public void highlightingCallback() {
+        // TODO: figure out what API to use to set highlights....
+
+    }
+}
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 17201cb..9edcba9a 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiExtension.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiExtension.java
@@ -47,6 +47,7 @@
     private final List<UiView> viewList;
     private final UiMessageHandlerFactory messageHandlerFactory;
     private final UiTopoOverlayFactory topoOverlayFactory;
+    private final UiTopo2OverlayFactory topo2OverlayFactory;
     private final UiTopoMapFactory topoMapFactory;
 
     private boolean isValid = true;
@@ -55,12 +56,14 @@
     private UiExtension(ClassLoader cl, String path, List<UiView> views,
                         UiMessageHandlerFactory mhFactory,
                         UiTopoOverlayFactory toFactory,
+                        UiTopo2OverlayFactory to2Factory,
                         UiTopoMapFactory tmFactory) {
         classLoader = cl;
         resourcePath = path;
         viewList = views;
         messageHandlerFactory = mhFactory;
         topoOverlayFactory = toFactory;
+        topo2OverlayFactory = to2Factory;
         topoMapFactory = tmFactory;
     }
 
@@ -122,6 +125,15 @@
     }
 
     /**
+     * Returns the topology-2 overlay factory, if one was defined.
+     *
+     * @return topology-2 overlay factory
+     */
+    public UiTopo2OverlayFactory topo2OverlayFactory() {
+        return topo2OverlayFactory;
+    }
+
+    /**
      * Returns the topology map factory, if one was defined.
      *
      * @return topology map factory
@@ -152,6 +164,7 @@
         private List<UiView> viewList = new ArrayList<>();
         private UiMessageHandlerFactory messageHandlerFactory = null;
         private UiTopoOverlayFactory topoOverlayFactory = null;
+        private UiTopo2OverlayFactory topo2OverlayFactory = null;
         private UiTopoMapFactory topoMapFactory = null;
 
         /**
@@ -205,6 +218,17 @@
         }
 
         /**
+         * Sets the topology-2 overlay factory for this extension.
+         *
+         * @param to2Factory topology-2 overlay factory
+         * @return self, for chaining
+         */
+        public Builder topo2OverlayFactory(UiTopo2OverlayFactory to2Factory) {
+            topo2OverlayFactory = to2Factory;
+            return this;
+        }
+
+        /**
          * Sets the topology map factory for this extension.
          *
          * @param tmFactory topology map factory
@@ -222,8 +246,8 @@
          */
         public UiExtension build() {
             return new UiExtension(classLoader, resourcePath, viewList,
-                                    messageHandlerFactory, topoOverlayFactory,
-                                    topoMapFactory);
+                                   messageHandlerFactory, topoOverlayFactory,
+                                   topo2OverlayFactory, topoMapFactory);
         }
     }
 }
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopo2Overlay.java b/core/api/src/main/java/org/onosproject/ui/UiTopo2Overlay.java
new file mode 100644
index 0000000..940080e
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopo2Overlay.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2017-present 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 a user interface topology-2 view overlay.
+ * <p>
+ * This base class does little more than provide a logger, an identifier,
+ * name, and glyph ID.
+ * Subclasses will probably want to override some or all of the base methods
+ * to do useful things during the life-cycle of the (topo-2) overlay.
+ */
+public class UiTopo2Overlay {
+
+    private static final String DEFAULT_GLYPH_ID = "m_topo";
+
+    /**
+     * Logger for this overlay.
+     */
+    protected final Logger log = LoggerFactory.getLogger(getClass());
+
+    private final String id;
+    private final String name;
+
+    private boolean isActive = false;
+
+    /**
+     * Creates a new user interface topology view overlay descriptor, with
+     * the given identifier and (human readable) name.
+     *
+     * @param id overlay identifier
+     * @param name overlay name
+     */
+    public UiTopo2Overlay(String id, String name) {
+        this.id = id;
+        this.name = name;
+    }
+
+    /**
+     * Returns the identifier for this overlay.
+     *
+     * @return the identifier
+     */
+    public String id() {
+        return id;
+    }
+
+    /**
+     * Returns the name for this overlay.
+     *
+     * @return the name
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Returns the glyph identifier to use in the toolbar.
+     * This implementation returns a default value. Subclasses may override
+     * this to provide the identity of a custom glyph.
+     *
+     * @return glyph ID
+     */
+    public String glyphId() {
+        return DEFAULT_GLYPH_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() {
+        isActive = true;
+    }
+
+    /**
+     * Callback invoked when this overlay is deactivated.
+     */
+    public void deactivate() {
+        isActive = false;
+    }
+
+    /**
+     * Returns true if this overlay is currently active.
+     *
+     * @return true if overlay active
+     */
+    public boolean isActive() {
+        return isActive;
+    }
+
+    /**
+     * Callback invoked to destroy this instance by cleaning up any
+     * internal state ready for garbage collection.
+     * This default implementation holds no state and does nothing.
+     */
+    public void destroy() {
+    }
+
+    /**
+     * Callback invoked when the topology highlighting should be updated.
+     * It is the implementation's responsibility to update the Model
+     * Highlighter state. This implementation does nothing.
+     */
+    public void highlightingCallback(/* ref to highlight model ? */) {
+
+    }
+}
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopo2OverlayFactory.java b/core/api/src/main/java/org/onosproject/ui/UiTopo2OverlayFactory.java
new file mode 100644
index 0000000..d6718fa
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopo2OverlayFactory.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2017-present 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-2
+ * overlay handlers specific to a given user interface connection.
+ */
+public interface UiTopo2OverlayFactory {
+
+    /**
+     * Produces a collection of new overlay handlers for topology-2 view.
+     *
+     * @return collection of new overlay handlers
+     */
+    Collection<UiTopo2Overlay> newOverlays();
+}
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 00295c4..c302c6b 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
@@ -28,9 +28,12 @@
 import org.onosproject.ui.UiExtensionService;
 import org.onosproject.ui.UiMessageHandler;
 import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiTopo2OverlayFactory;
 import org.onosproject.ui.UiTopoLayoutService;
 import org.onosproject.ui.UiTopoOverlayFactory;
 import org.onosproject.ui.impl.topo.Topo2Jsonifier;
+import org.onosproject.ui.impl.topo.Topo2OverlayCache;
+import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
 import org.onosproject.ui.impl.topo.UiTopoSession;
 import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
 import org.onosproject.ui.model.topo.UiTopoLayout;
@@ -50,7 +53,6 @@
     private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
 
     private static final String EVENT = "event";
-    private static final String SID = "sid";
     private static final String PAYLOAD = "payload";
     private static final String UNKNOWN = "unknown";
 
@@ -81,6 +83,7 @@
 
     private Map<String, UiMessageHandler> handlers;
     private TopoOverlayCache overlayCache;
+    private Topo2OverlayCache overlay2Cache;
 
     /**
      * Creates a new web-socket for serving data to the Web UI.
@@ -190,7 +193,7 @@
         topoSession.destroy();
         destroyHandlersAndOverlays();
         log.info("GUI client disconnected [close-code={}, message={}]",
-                closeCode, message);
+                 closeCode, message);
     }
 
     @Override
@@ -244,6 +247,7 @@
         log.debug("Creating handlers and overlays...");
         handlers = new HashMap<>();
         overlayCache = new TopoOverlayCache();
+        overlay2Cache = new Topo2OverlayCache();
 
         UiExtensionService service = directory.get(UiExtensionService.class);
         service.getExtensions().forEach(ext -> {
@@ -255,10 +259,13 @@
                         handler.messageTypes().forEach(type -> handlers.put(type, handler));
 
                         // need to inject the overlay cache into topology message handler
-                        // TODO: code for Topo2ViewMessageHandler required here
                         if (handler instanceof TopologyViewMessageHandler) {
                             ((TopologyViewMessageHandler) handler).setOverlayCache(overlayCache);
                         }
+
+                        if (handler instanceof Topo2ViewMessageHandler) {
+                            ((Topo2ViewMessageHandler) handler).setOverlayCache(overlay2Cache);
+                        }
                     } catch (Exception e) {
                         log.warn("Unable to setup handler {} due to", handler, e);
                     }
@@ -269,9 +276,14 @@
             if (overlayFactory != null) {
                 overlayFactory.newOverlays().forEach(overlayCache::add);
             }
+
+            UiTopo2OverlayFactory overlay2Factory = ext.topo2OverlayFactory();
+            if (overlay2Factory != null) {
+                overlay2Factory.newOverlays().forEach(overlay2Cache::add);
+            }
         });
         log.debug("#handlers = {}, #overlays = {}", handlers.size(),
-                overlayCache.size());
+                  overlayCache.size());
     }
 
     // Destroys message handlers.
@@ -298,7 +310,7 @@
                     .put(ID, node.id().toString())
                     .put(IP, node.ip().toString())
                     .put(GlyphConstants.UI_ATTACHED,
-                            node.equals(service.getLocalNode()));
+                         node.equals(service.getLocalNode()));
             instances.add(instance);
         }
 
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2OverlayCache.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2OverlayCache.java
new file mode 100644
index 0000000..1c05acf
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2OverlayCache.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2017-present 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.topo;
+
+import org.onosproject.ui.UiTopo2Overlay;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * A cache of {@link org.onosproject.ui.UiTopo2Overlay}'s that were
+ * registered at the time the UI connection was established.
+ * <p>
+ * Note, for now, this is a simplified version which will only cache
+ * a single overlay. At some future point, this should be expanded to mirror
+ * the behavior of {@link org.onosproject.ui.impl.TopoOverlayCache}.
+ */
+public class Topo2OverlayCache {
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    private static final String EMPTY = "";
+    private static final String NO_OVERLAY = "No Overlay";
+    private static final String UNKNOWN = "unknown";
+
+    private static final UiTopo2Overlay NONE = new NullOverlay();
+
+    private final Map<String, UiTopo2Overlay> overlays = new HashMap<>();
+    private UiTopo2Overlay current = null;
+
+    /**
+     * Constructs the overlay cache.
+     */
+    public Topo2OverlayCache() {
+        overlays.put(null, NONE);
+    }
+
+    /**
+     * Adds a topology-2 overlay to the cache.
+     *
+     * @param overlay a topology-2 overlay
+     */
+    public void add(UiTopo2Overlay overlay) {
+        overlays.put(overlay.id(), overlay);
+        log.warn("added overlay: " + 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) {
+        UiTopo2Overlay toDeactivate = getOverlay(deact);
+        UiTopo2Overlay toActivate = getOverlay(act);
+
+        toDeactivate.deactivate();
+        current = toActivate;
+        current.activate();
+    }
+
+    private UiTopo2Overlay getOverlay(String id) {
+        return isNullOrEmpty(id) ? NONE : overlays.get(id);
+    }
+
+    /**
+     * Returns the current overlay instance.
+     * Note that this method always returns a reference; when there is no
+     * overlay selected the "NULL" overlay instance is returned.
+     *
+     * @return the current overlay
+     */
+    public UiTopo2Overlay currentOverlay() {
+        return current;
+    }
+
+    /**
+     * Returns the number of overlays in the cache. Remember that this
+     * includes the "NULL" overlay, representing "no overlay selected".
+     *
+     * @return number of overlays
+     */
+    public int size() {
+        return overlays.size();
+    }
+
+    /**
+     * Returns true if the identifier of the currently active overlay
+     * matches the given parameter.
+     *
+     * @param overlayId overlay identifier
+     * @return true if this matches the ID of currently active overlay
+     */
+    public boolean isActive(String overlayId) {
+        return currentOverlay().id().equals(overlayId);
+    }
+
+    /**
+     * Returns the collection of registered overlays.
+     *
+     * @return registered overlays
+     */
+    public Collection<UiTopo2Overlay> list() {
+        return overlays.values();
+    }
+
+    // overlay instance representing "no overlay selected"
+    private static class NullOverlay extends UiTopo2Overlay {
+        NullOverlay() {
+            super(EMPTY, NO_OVERLAY);
+        }
+
+        @Override
+        public String glyphId() {
+            return UNKNOWN;
+        }
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
index 3c93110..1763048 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/topo/Topo2ViewMessageHandler.java
@@ -16,12 +16,14 @@
 
 package org.onosproject.ui.impl.topo;
 
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.google.common.collect.ImmutableSet;
 import org.onlab.osgi.ServiceDirectory;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
 import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.UiTopo2Overlay;
 import org.onosproject.ui.impl.UiWebSocket;
 import org.onosproject.ui.model.topo.UiClusterMember;
 import org.onosproject.ui.model.topo.UiNode;
@@ -67,10 +69,12 @@
     private static final String CURRENT_LAYOUT = "topo2CurrentLayout";
     private static final String CURRENT_REGION = "topo2CurrentRegion";
     private static final String PEER_REGIONS = "topo2PeerRegions";
+    private static final String OVERLAYS = "topo2Overlays";
 
 
     private UiTopoSession topoSession;
     private Topo2Jsonifier t2json;
+    private Topo2OverlayCache overlay2Cache;
 
 
     @Override
@@ -82,6 +86,17 @@
         t2json = new Topo2Jsonifier(directory, connection.userName());
     }
 
+    /**
+     * Sets a reference to the overlay cache for interacting with registered
+     * overlays.
+     *
+     * @param overlay2Cache the overlay cache
+     */
+    public void setOverlayCache(Topo2OverlayCache overlay2Cache) {
+        this.overlay2Cache = overlay2Cache;
+    }
+
+
     @Override
     protected Collection<RequestHandler> createRequestHandlers() {
         return ImmutableSet.of(
@@ -114,6 +129,25 @@
         return peersPayload;
     }
 
+    private ObjectNode mkOverlaysMessage() {
+        ArrayNode a = arrayNode();
+        for (UiTopo2Overlay ov : overlay2Cache.list()) {
+            a.add(json(ov));
+        }
+        ObjectNode payload = objectNode();
+        payload.set("overlays", a);
+        return payload;
+    }
+
+    private ObjectNode json(UiTopo2Overlay ov) {
+        return objectNode()
+                .put("id", ov.id())
+                .put("name", ov.name())
+                .put("gid", ov.glyphId());
+    }
+
+    // ==================================================================
+
 
     private final class Topo2Start extends RequestHandler {
         private Topo2Start() {
@@ -152,6 +186,9 @@
 
             // these are the regions/devices that are siblings to this region
             sendMessage(PEER_REGIONS, mkPeersMessage(currentLayout));
+
+            // these are the registered overlays
+            sendMessage(OVERLAYS, mkOverlaysMessage());
         }
     }