ONOS-4646: Provide temp. mechanism for topology overlays to modify link details data.

Change-Id: I00b78b1da1580883e09af87ed470e6142a1ec19b
diff --git a/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java b/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
index 7175bc6..b598bdd 100644
--- a/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
+++ b/core/api/src/main/java/org/onosproject/ui/UiTopoOverlay.java
@@ -18,10 +18,13 @@
 
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.HostId;
+import org.onosproject.net.link.LinkEvent;
 import org.onosproject.ui.topo.PropertyPanel;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Map;
+
 /**
  * Represents user interface topology view overlay.
  */
@@ -106,7 +109,7 @@
      * a selected device.
      * This default implementation does nothing.
      *
-     * @param pp property panel model of summary data
+     * @param pp       property panel model of summary data
      * @param deviceId device id
      */
     public void modifyDeviceDetails(PropertyPanel pp, DeviceId deviceId) {
@@ -117,9 +120,29 @@
      * a selected host.
      * This default implementation does nothing.
      *
-     * @param pp property panel model of summary data
+     * @param pp     property panel model of summary data
      * @param hostId host id
      */
     public void modifyHostDetails(PropertyPanel pp, HostId hostId) {
     }
+
+    /**
+     * Callback invoked when a link event is processed (e.g. link added).
+     * A subclass may override this method to return a map of property
+     * key/value pairs to be included in the JSON event back to the client,
+     * so that those additional properties are available to be displayed as
+     * link details.
+     * <p>
+     * The default implementation returns {@code null}, that is, no additional
+     * properties to be added.
+     *
+     * @param event the link event
+     * @return map of additional key/value pairs to be added to the JSON event
+     * @deprecated this is a temporary addition for Goldeneye (1.6) release,
+     * and expected to be replaced in the Hummingbird (1.7) release
+     */
+    @Deprecated
+    public Map<String, String> additionalLinkData(LinkEvent event) {
+        return null;
+    }
 }
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 57c5a7b..7ba4609 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
@@ -71,6 +71,7 @@
 import java.util.Comparator;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
@@ -128,6 +129,8 @@
     private static final String TOPO_START_DONE = "topoStartDone";
 
     // fields
+    private static final String PAYLOAD = "payload";
+    private static final String EXTRA = "extra";
     private static final String ID = "id";
     private static final String KEY = "key";
     private static final String APP_ID = "appId";
@@ -603,7 +606,7 @@
         Collections.sort(nodes, NODE_COMPARATOR);
         for (ControllerNode node : nodes) {
             sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node),
-                                        messageType));
+                    messageType));
         }
     }
 
@@ -612,13 +615,13 @@
         // Send optical first, others later for layered rendering
         for (Device device : deviceService.getDevices()) {
             if ((device.type() == Device.Type.ROADM) ||
-                    (device.type() == Device.Type.OTN))  {
+                    (device.type() == Device.Type.OTN)) {
                 sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
             }
         }
         for (Device device : deviceService.getDevices()) {
             if ((device.type() != Device.Type.ROADM) &&
-                    (device.type() != Device.Type.OTN))  {
+                    (device.type() != Device.Type.OTN)) {
                 sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
             }
         }
@@ -629,16 +632,40 @@
         // Send optical first, others later for layered rendering
         for (Link link : linkService.getLinks()) {
             if (link.type() == Link.Type.OPTICAL) {
-                sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
+                sendMessage(composeLinkMessage(new LinkEvent(LINK_ADDED, link)));
             }
         }
         for (Link link : linkService.getLinks()) {
             if (link.type() != Link.Type.OPTICAL) {
-                sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
+                sendMessage(composeLinkMessage(new LinkEvent(LINK_ADDED, link)));
             }
         }
     }
 
+    // Temporary mechanism to support topology overlays adding their own
+    // properties to the link events.
+    private ObjectNode composeLinkMessage(LinkEvent event) {
+        // start with base message
+        ObjectNode msg = linkMessage(event);
+        Map<String, String> additional =
+                overlayCache.currentOverlay().additionalLinkData(event);
+
+        if (additional != null) {
+            // attach additional key-value pairs as extra data structure
+            ObjectNode payload = (ObjectNode) msg.get(PAYLOAD);
+            payload.set(EXTRA, createExtra(additional));
+        }
+        return msg;
+    }
+
+    private ObjectNode createExtra(Map<String, String> additional) {
+        ObjectNode extra = objectNode();
+        for (Map.Entry<String, String> entry : additional.entrySet()) {
+            extra.put(entry.getKey(), entry.getValue());
+        }
+        return extra;
+    }
+
     // Sends all hosts to the client as host-added messages.
     private void sendAllHosts() {
         for (Host host : hostService.getHosts()) {
@@ -759,7 +786,7 @@
     private class InternalLinkListener implements LinkListener {
         @Override
         public void event(LinkEvent event) {
-            msgSender.execute(() -> sendMessage(linkMessage(event)));
+            msgSender.execute(() -> sendMessage(composeLinkMessage(event)));
             msgSender.execute(traffic::pokeIntent);
             eventAccummulator.add(event);
         }
@@ -829,7 +856,7 @@
             String me = this.toString();
             String miniMe = me.replaceAll("^.*@", "me@");
             log.debug("Time: {}; this: {}, processing items ({} events)",
-                      now, miniMe, items.size());
+                    now, miniMe, items.size());
             // End-of-Debugging
 
             try {
diff --git a/web/gui/src/main/webapp/app/view/topo/topoLink.js b/web/gui/src/main/webapp/app/view/topo/topoLink.js
index 7a9ad1d..95a9daa 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoLink.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoLink.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // injected refs
-    var $log, fs, sus, ts, flash, tss, tps;
+    var $log, fs, sus, ts, flash, tss, tps, tov;
 
     // internal state
     var api,
@@ -238,7 +238,7 @@
 
         d.el.classed('selected', true);
 
-        tps.displayLink(d);
+        tps.displayLink(d, tov.hooks.modifyLinkData);
         tps.displaySomething();
     }
 
@@ -300,9 +300,9 @@
     angular.module('ovTopo')
         .factory('TopoLinkService',
         ['$log', 'FnService', 'SvgUtilService', 'ThemeService', 'FlashService',
-            'TopoSelectService', 'TopoPanelService',
+            'TopoSelectService', 'TopoPanelService', 'TopoOverlayService',
 
-        function (_$log_, _fs_, _sus_, _ts_, _flash_, _tss_, _tps_) {
+        function (_$log_, _fs_, _sus_, _ts_, _flash_, _tss_, _tps_, _tov_) {
             $log = _$log_;
             fs = _fs_;
             sus = _sus_;
@@ -310,6 +310,7 @@
             flash = _flash_;
             tss = _tss_;
             tps = _tps_;
+            tov = _tov_;
 
             function initLink(_api_, _td3_) {
                 api = _api_;
diff --git a/web/gui/src/main/webapp/app/view/topo/topoModel.js b/web/gui/src/main/webapp/app/view/topo/topoModel.js
index d8f279b..d104449 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoModel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoModel.js
@@ -230,7 +230,8 @@
                     ws = (s && s.linkWidth) || 0,
                     wt = (t && t.linkWidth) || 0;
                 return lnk.position.multiLink ? 5 : Math.max(ws, wt);
-            }
+            },
+            extra: link.extra
         });
         return lnk;
     }
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 0add26c..b04bd53 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOverlay.js
@@ -277,6 +277,13 @@
         cb && cb();
     }
 
+    // Temporary function to allow overlays to modify link detail data
+    // in the client. (In the near future, this will be done on the server).
+    function modifyLinkDataHook(data, extra) {
+        var cb = _hook('modifylinkdata');
+        return cb && extra ? cb(data, extra) : data;
+    }
+
     // === -----------------------------------------------------
     //  Event (from server) Handlers
 
@@ -427,7 +434,8 @@
                     singleSelect: singleSelectHook,
                     multiSelect: multiSelectHook,
                     mouseOver: mouseOverHook,
-                    mouseOut: mouseOutHook
+                    mouseOut: mouseOutHook,
+                    modifyLinkData: modifyLinkDataHook
                 },
 
                 showHighlights: showHighlights
diff --git a/web/gui/src/main/webapp/app/view/topo/topoPanel.js b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
index f5730dd..edea27b 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
@@ -309,7 +309,7 @@
     var coreOrder = [
             'Type', 'Expected', '-',
             'A_type', 'A_id', 'A_label', 'A_port', '-',
-            'B_type', 'B_id', 'B_label', 'B_port', '-'
+            'B_type', 'B_id', 'B_label', 'B_port'
         ],
         edgeOrder = [
             'Type', '-',
@@ -317,7 +317,7 @@
             'B_type', 'B_id', 'B_label', 'B_port'
         ];
 
-    function displayLink(data) {
+    function displayLink(data, modifyCb) {
         detail.setup();
 
         var svg = detail.appendHeader('div')
@@ -332,9 +332,8 @@
         gs.addGlyph(svg, 'ports', 40);
         title.text('Link');
 
-
-        listProps(tbody, {
-            propOrder: order,
+        var linkData = {
+            propOrder: order.slice(0),      // makes a copy of the array
             props: {
                 Type: linkType(data),
                 Expected: linkExpected(data),
@@ -349,9 +348,11 @@
                 B_label: friendly(data.target),
                 B_port: data.tgtPort
             }
-        });
+        };
+        listProps(tbody, modifyCb(linkData, data.extra));
 
         if (!edgeLink) {
+            addSep(tbody);
             addProp(tbody, 'A &rarr; B', linkSummary(data.fromSource));
             addProp(tbody, 'B &rarr; A', linkSummary(data.fromTarget));
         }