[ONOS-5523]ProtectedIntentsOverlay

Change-Id: Ief409aacf7e82655881f658718ac0ca50a3c8cc9
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
new file mode 100644
index 0000000..5d928a0
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentMonitor.java
@@ -0,0 +1,337 @@
+/*
+ * 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;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.ElementId;
+import org.onosproject.net.HostId;
+import org.onosproject.net.Link;
+import org.onosproject.net.MarkerResource;
+import org.onosproject.net.NetworkResource;
+import org.onosproject.net.behaviour.protection.TransportEndpointDescription;
+import org.onosproject.net.intent.FlowRuleIntent;
+import org.onosproject.net.intent.Intent;
+import org.onosproject.net.intent.OpticalConnectivityIntent;
+import org.onosproject.net.intent.ProtectionEndpointIntent;
+import org.onosproject.ui.impl.topo.util.ServicesBundle;
+import org.onosproject.ui.impl.topo.util.TrafficLink;
+import org.onosproject.ui.impl.topo.util.TrafficLink.StatsType;
+import org.onosproject.ui.impl.topo.util.TrafficLinkMap;
+import org.onosproject.ui.topo.AbstractTopoMonitor;
+import org.onosproject.ui.topo.DeviceHighlight;
+import org.onosproject.ui.topo.Highlights;
+import org.onosproject.ui.topo.HostHighlight;
+import org.onosproject.ui.topo.LinkHighlight.Flavor;
+import org.onosproject.ui.topo.NodeHighlight;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.IDLE;
+import static org.onosproject.ui.impl.ProtectedIntentMonitor.ProtectedMode.SELECTED_INTENT;
+
+/**
+ * Encapsulates the behavior of monitoring protected intents.
+ */
+//TODO refactor duplicated methods from here and the TrafficMonitor to AbstractTopoMonitor
+public class ProtectedIntentMonitor extends AbstractTopoMonitor {
+
+    private static final Logger log =
+            LoggerFactory.getLogger(ProtectedIntentMonitor.class);
+    public static final String PRIMARY_PATH_TAG = "protection1";
+
+    /**
+     * Designates the different modes of operation.
+     */
+    public enum ProtectedMode {
+        IDLE,
+        SELECTED_INTENT
+    }
+
+    private final long trafficPeriod;
+    private final ServicesBundle servicesBundle;
+    private final TopologyViewMessageHandler msgHandler;
+
+    private final Timer timer = new Timer("topo-protected-intents");
+
+    private TimerTask trafficTask = null;
+    private ProtectedMode mode = ProtectedMode.IDLE;
+    private Intent selectedIntent = null;
+
+
+    /**
+     * Constructs a protected intent monitor.
+     *
+     * @param trafficPeriod  traffic task period in ms
+     * @param servicesBundle bundle of services
+     * @param msgHandler     our message handler
+     */
+    public ProtectedIntentMonitor(long trafficPeriod, ServicesBundle servicesBundle,
+                                  TopologyViewMessageHandler msgHandler) {
+        this.trafficPeriod = trafficPeriod;
+        this.servicesBundle = servicesBundle;
+        this.msgHandler = msgHandler;
+
+    }
+
+    // =======================================================================
+    // === API ===
+
+    // TODO: move this out to the "h2h/multi-intent app"
+
+    /**
+     * Monitor for protected intent data to be sent back to the web client,
+     * for the given intent.
+     *
+     * @param intent the intent to monitor
+     */
+    public synchronized void monitor(Intent intent) {
+        log.debug("monitor intent: {}", intent.id());
+        selectedIntent = intent;
+        mode = SELECTED_INTENT;
+        scheduleTask();
+        sendSelectedIntents();
+    }
+
+    /**
+     * Stop all traffic monitoring.
+     */
+    public synchronized void stopMonitoring() {
+        log.debug("STOP monitoring");
+        if (mode != IDLE) {
+            sendClearAll();
+        }
+    }
+
+
+    // =======================================================================
+    // === Helper methods ===
+    private void sendClearAll() {
+        clearAll();
+        sendClearHighlights();
+    }
+
+    private void clearAll() {
+        this.mode = IDLE;
+        clearSelection();
+        cancelTask();
+    }
+
+    private void clearSelection() {
+        selectedIntent = null;
+    }
+
+    //TODO duplicate and can be brought in abstract upper class.
+    private synchronized void scheduleTask() {
+        if (trafficTask == null) {
+            log.debug("Starting up background protected intent task...");
+            trafficTask = new TrafficUpdateTask();
+            timer.schedule(trafficTask, trafficPeriod, trafficPeriod);
+        } else {
+            log.debug("(protected intent task already running)");
+        }
+    }
+
+    private synchronized void cancelTask() {
+        if (trafficTask != null) {
+            trafficTask.cancel();
+            trafficTask = null;
+        }
+    }
+
+    private void sendSelectedIntents() {
+        log.debug("sendSelectedIntents: {}", selectedIntent);
+        msgHandler.sendHighlights(protectedIntentHighlights());
+    }
+
+    private void sendClearHighlights() {
+        log.debug("sendClearHighlights");
+        msgHandler.sendHighlights(new Highlights());
+    }
+
+    // =======================================================================
+    // === Generate messages in JSON object node format
+    private Highlights protectedIntentHighlights() {
+        Highlights highlights = new Highlights();
+        TrafficLinkMap linkMap = new TrafficLinkMap();
+        if (selectedIntent != null) {
+            List<Intent> installables = servicesBundle.intentService()
+                    .getInstallableIntents(selectedIntent.key());
+            Set<Link> primary = new HashSet<>();
+            Set<Link> backup = new HashSet<>();
+            if (installables != null) {
+                //There should be exactly two FlowRuleIntent and four
+                //ProtectionEndpointIntent for each ProtectedTransportIntent.
+                for (Intent installable : installables) {
+                    if (installable instanceof FlowRuleIntent) {
+                        handleFlowRuleIntent(primary, backup, (FlowRuleIntent) installable);
+                    } else if (installable instanceof ProtectionEndpointIntent) {
+                        handleProtectionEndpointIntent(primary, backup,
+                                                       (ProtectionEndpointIntent) installable);
+                    } else {
+                        log.warn("Intent {} is not an expected installable type {} " +
+                                         "related to ProtectedTransportIntent",
+                                 installable.id(), installable.getClass().getSimpleName());
+                        stopMonitoring();
+                    }
+                }
+                boolean isOptical = selectedIntent instanceof OpticalConnectivityIntent;
+                //last parameter (traffic) signals if the link is highlited with ants or solid line
+                //Flavor is swapped so green is primary path.
+                if (usingBackup(primary)) {
+                    //the backup becomes in use so we have a dotted line
+                    processLinks(linkMap, backup, Flavor.PRIMARY_HIGHLIGHT, isOptical, true);
+                } else {
+                    processLinks(linkMap, primary, Flavor.SECONDARY_HIGHLIGHT, isOptical, true);
+                    processLinks(linkMap, backup, Flavor.PRIMARY_HIGHLIGHT, isOptical, false);
+                }
+                updateHighlights(highlights, primary);
+                updateHighlights(highlights, backup);
+                colorLinks(highlights, linkMap);
+                highlights.subdueAllElse(Highlights.Amount.MINIMALLY);
+            } else {
+                log.debug("Selected Intent has no installables intents");
+            }
+        } else {
+            log.debug("Selected Intent is null");
+        }
+        return highlights;
+    }
+
+    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+    private void handleProtectionEndpointIntent(Set<Link> primary, Set<Link> backup,
+                                                ProtectionEndpointIntent peIntent) {
+        TransportEndpointDescription primaryDesc = peIntent
+                .description().paths().get(0);
+        TransportEndpointDescription secondaryDesc = peIntent
+                .description().paths().get(1);
+        primary.addAll(servicesBundle.linkService()
+                               .getLinks(primaryDesc.output()
+                                                 .connectPoint()));
+        backup.addAll(servicesBundle.linkService()
+                              .getLinks(secondaryDesc.output()
+                                                .connectPoint()));
+    }
+
+    private void handleFlowRuleIntent(Set<Link> primary, Set<Link> backup,
+                                      FlowRuleIntent frIntent) {
+        boolean protection1 = frIntent.resources().stream()
+                .filter(r -> r instanceof MarkerResource)
+                .map(NetworkResource::toString)
+                .anyMatch(rstring -> rstring.equals(PRIMARY_PATH_TAG));
+        if (protection1) {
+            primary.addAll(linkResources(frIntent));
+        } else {
+            backup.addAll(linkResources(frIntent));
+        }
+    }
+
+    // returns true if the backup path is the one where the traffic is currently flowing
+    private boolean usingBackup(Set<Link> primary) {
+        Set<Link> activeLinks = Sets.newHashSet(servicesBundle.linkService().getActiveLinks());
+        return primary.isEmpty() || !activeLinks.containsAll(primary);
+    }
+
+    private void updateHighlights(Highlights highlights, Iterable<Link> links) {
+        for (Link link : links) {
+            ensureNodePresent(highlights, link.src().elementId());
+            ensureNodePresent(highlights, link.dst().elementId());
+        }
+    }
+
+    //TODO duplicate and can be brought in abstract upper class.
+    private void ensureNodePresent(Highlights highlights, ElementId eid) {
+        String id = eid.toString();
+        NodeHighlight nh = highlights.getNode(id);
+        if (nh == null) {
+            if (eid instanceof DeviceId) {
+                nh = new DeviceHighlight(id);
+                highlights.add((DeviceHighlight) nh);
+            } else if (eid instanceof HostId) {
+                nh = new HostHighlight(id);
+                highlights.add((HostHighlight) nh);
+            }
+        }
+    }
+
+    private void processLinks(TrafficLinkMap linkMap, Iterable<Link> links,
+                              Flavor flavor, boolean isOptical,
+                              boolean showTraffic) {
+        if (links != null) {
+            for (Link link : links) {
+                TrafficLink tlink = linkMap.add(link);
+                tlink.tagFlavor(flavor);
+                tlink.optical(isOptical);
+                if (showTraffic) {
+                    tlink.antMarch(true);
+                }
+            }
+        }
+    }
+
+    //TODO duplicate and can be brought in abstract upper class.
+    private void colorLinks(Highlights highlights, TrafficLinkMap linkMap) {
+        for (TrafficLink tlink : linkMap.biLinks()) {
+            highlights.add(tlink.highlight(StatsType.TAGGED));
+        }
+    }
+
+    //TODO duplicate and can be brought in abstract upper class.
+    // Extracts links from the specified flow rule intent resources
+    private Collection<Link> linkResources(Intent installable) {
+        ImmutableList.Builder<Link> builder = ImmutableList.builder();
+        installable.resources().stream().filter(r -> r instanceof Link)
+                .forEach(r -> builder.add((Link) r));
+        return builder.build();
+    }
+
+    // =======================================================================
+    // === Background Task
+
+    // Provides periodic update of traffic information to the client
+    private class TrafficUpdateTask extends TimerTask {
+        @Override
+        public void run() {
+            try {
+                switch (mode) {
+                    case SELECTED_INTENT:
+                        sendSelectedIntents();
+                        break;
+
+                    default:
+                        // RELATED_INTENTS and IDLE modes should never invoke
+                        // the background task, but if they do, they have
+                        // nothing to do
+                        break;
+                }
+
+            } catch (Exception e) {
+                log.warn("Unable to process protected intent task due to {}", e.getMessage());
+                log.warn("Boom!", e);
+            }
+        }
+    }
+}
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentOverlay.java b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentOverlay.java
new file mode 100644
index 0000000..eab07e2
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/ProtectedIntentOverlay.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import org.onosproject.ui.UiTopoOverlay;
+
+/**
+ * Topology Overlay for network traffic.
+ */
+public class ProtectedIntentOverlay extends UiTopoOverlay {
+    /**
+     * Traffic Overlay identifier.
+     */
+    public static final String PROTECTED_INTENTS_ID = "protectedIntent";
+
+    public ProtectedIntentOverlay() {
+        super(PROTECTED_INTENTS_ID);
+    }
+
+    // override activate and deactivate, to write log messages
+    @Override
+    public void activate() {
+        super.activate();
+        log.debug("ProtectedIntentOverlay Activated");
+    }
+
+    @Override
+    public void deactivate() {
+        super.deactivate();
+        log.debug("ProtectedIntentOverlay Deactivated");
+    }
+}
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 bd344db..ddf64bd 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
@@ -55,8 +55,8 @@
 import org.onosproject.net.intent.IntentListener;
 import org.onosproject.net.intent.Key;
 import org.onosproject.net.intent.MultiPointToSinglePointIntent;
-import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.intent.IntentService;
+import org.onosproject.net.intent.IntentState;
 import org.onosproject.net.link.LinkEvent;
 import org.onosproject.net.link.LinkListener;
 import org.onosproject.ui.JsonUtils;
@@ -124,6 +124,10 @@
     private static final String TOPO_SELECT_OVERLAY = "topoSelectOverlay";
     private static final String TOPO_STOP = "topoStop";
 
+    //Protected Intents events
+    private static final String SEL_PROTECTED_INTENT = "selectProtectedIntent";
+    private static final String CANCEL_PROTECTED_INTENT_HIGHLIGHT = "cancelProtectedIntentHighlight";
+
     // outgoing event types
     private static final String SHOW_SUMMARY = "showSummary";
     private static final String SHOW_DETAILS = "showDetails";
@@ -186,6 +190,7 @@
 
     private TopoOverlayCache overlayCache;
     private TrafficMonitor traffic;
+    private ProtectedIntentMonitor protectedIntentMonitor;
 
     private TimerTask summaryTask = null;
     private boolean summaryRunning = false;
@@ -198,6 +203,7 @@
         super.init(connection, directory);
         appId = directory.get(CoreService.class).registerApplication(MY_APP_ID);
         traffic = new TrafficMonitor(TRAFFIC_PERIOD, servicesBundle, this);
+        protectedIntentMonitor = new ProtectedIntentMonitor(TRAFFIC_PERIOD, servicesBundle, this);
     }
 
     @Override
@@ -236,8 +242,10 @@
                 new ReqPrevIntent(),
                 new ReqSelectedIntentTraffic(),
                 new SelIntent(),
+                new SelProtectedIntent(),
 
-                new CancelTraffic()
+                new CancelTraffic(),
+                new CancelProtectedIntentHighlight()
         );
     }
 
@@ -629,6 +637,23 @@
         }
     }
 
+    private final class SelProtectedIntent extends RequestHandler {
+        private SelProtectedIntent() {
+            super(SEL_PROTECTED_INTENT);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            Intent intent = findIntentByPayload(payload);
+            if (intent == null) {
+                log.warn("Unable to find protected intent from payload {}", payload);
+            } else {
+                log.debug("starting to monitor protected intent {}", intent.key());
+                protectedIntentMonitor.monitor(intent);
+            }
+        }
+    }
+
     private final class CancelTraffic extends RequestHandler {
         private CancelTraffic() {
             super(CANCEL_TRAFFIC);
@@ -640,6 +665,17 @@
         }
     }
 
+    private final class CancelProtectedIntentHighlight extends RequestHandler {
+        private CancelProtectedIntentHighlight() {
+            super(CANCEL_PROTECTED_INTENT_HIGHLIGHT);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            protectedIntentMonitor.stopMonitoring();
+        }
+    }
+
     //=======================================================================
 
     // Converts highlights to JSON format and sends the message to the client
@@ -666,7 +702,7 @@
         nodes.sort(NODE_COMPARATOR);
         for (ControllerNode node : nodes) {
             sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node),
-                    messageType));
+                                        messageType));
         }
     }
 
@@ -916,7 +952,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/java/org/onosproject/ui/impl/UiExtensionManager.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiExtensionManager.java
index 254e807..14b3db9 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
@@ -172,7 +172,8 @@
 
         UiTopoOverlayFactory topoOverlayFactory =
                 () -> ImmutableList.of(
-                        new TrafficOverlay()
+                        new TrafficOverlay(),
+                        new ProtectedIntentOverlay()
                 );
 
         UiTopoMapFactory topoMapFactory =
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 948939b..03d8477 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -24,7 +24,7 @@
 
     // injected refs
     var $log, $timeout, fs, sus, ts, flash, wss, tov,
-        tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg;
+        tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg, tpis;
 
     // configuration
     var linkConfig = {
@@ -1052,9 +1052,10 @@
             'TopoOverlayService', 'TopoInstService', 'TopoModelService',
             'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
             'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
+            'TopoProtectedIntentsService',
 
         function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_, _tov_,
-                  _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
+                  _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_, _tpis_) {
             $log = _$log_;
             $timeout = _$timeout_;
             fs = _fs_;
@@ -1071,6 +1072,7 @@
             tos = _tos_;
             fltr = _fltr_;
             tls = _tls_;
+            tpis = _tpis_;
 
             ts.addListener(updateLinksAndNodes);
 
@@ -1093,6 +1095,7 @@
                 td3.initD3(mkD3Api());
                 tss.initSelect(mkSelectApi());
                 tts.initTraffic(mkTrafficApi());
+                tpis.initProtectedIntents(mkTrafficApi());
                 tos.initOblique(mkObliqueApi(uplink, fltr));
                 fltr.initFilter(mkFilterApi());
                 tls.initLink(mkLinkApi(svg, uplink), td3);
@@ -1136,6 +1139,7 @@
                 tls.destroyLink();
                 tos.destroyOblique();
                 tts.destroyTraffic();
+                tpis.destroyProtectedIntents();
                 tss.destroySelect();
                 td3.destroyD3();
                 tms.destroyModel();
diff --git a/web/gui/src/main/webapp/app/view/topo/topoProtectedIntent.js b/web/gui/src/main/webapp/app/view/topo/topoProtectedIntent.js
new file mode 100644
index 0000000..20193dd
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoProtectedIntent.js
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Topology Protected Intents Module.
+ Defines behavior for viewing protected intents .
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, fs, flash, wss, api;
+
+    // internal state
+    var showingProtectedIntent = null;
+
+    // === -------------------------------------------------------------
+    //  protected intent requests invoked from keystrokes or toolbar buttons...
+
+    function cancelHighlights() {
+        if (!showingProtectedIntent) {
+            return false;
+        }
+
+        showingProtectedIntent = false;
+        wss.sendEvent('cancelProtectedIntentHighlight');
+        flash.flash('Monitoring canceled');
+        return true;
+    }
+
+    // force the system to create a single intent selection
+    function showProtectedIntent(data) {
+        wss.sendEvent('selectProtectedIntent', data);
+        flash.flash('Selecting Intent ' + data.key);
+        showingProtectedIntent = true;
+    }
+
+    // === -----------------------------------------------------
+    // === MODULE DEFINITION ===
+
+    angular.module('ovTopo')
+    .factory('TopoProtectedIntentsService',
+        ['$log', 'FnService', 'FlashService', 'WebSocketService',
+
+        function (_$log_, _fs_, _flash_, _wss_) {
+            $log = _$log_;
+            fs = _fs_;
+            flash = _flash_;
+            wss = _wss_;
+
+            return {
+                initProtectedIntents: function (_api_) { api = _api_; },
+                destroyProtectedIntents: function () { },
+
+                // invoked from toolbar overlay buttons or keystrokes
+                cancelHighlights: cancelHighlights,
+                showProtectedIntent: showProtectedIntent
+            };
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoProtectedIntentOverlay.js b/web/gui/src/main/webapp/app/view/topo/topoProtectedIntentOverlay.js
new file mode 100644
index 0000000..ab9114f
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoProtectedIntentOverlay.js
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+/*
+ ONOS GUI -- Topology Protected Intents Overlay Module.
+ Defines behavior for viewing different multiple Protected Intents.
+ Installed as a Topology Overlay.
+ */
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, tov, tpis;
+
+    // NOTE: no internal state here -- see topoProtectedIntents for that
+
+    // NOTE: providing button disabling requires too big a refactoring of
+    //       the button factory etc. Will have to be done another time.
+
+
+    // traffic overlay definition
+    var overlay = {
+        overlayId: 'protectedIntent',
+        glyphId: 'm_ips',
+        tooltip: 'Protected Intents Overlay',
+
+        activate: function () {
+            $log.debug("Protected Intent overlay ACTIVATED");
+        },
+
+        deactivate: function () {
+            tpis.cancelHighlights();
+            $log.debug("Protected Intent DEACTIVATED");
+        },
+
+        hooks: {
+            // hook for handling escape key
+            escape: function () {
+                // Must return true to consume ESC, false otherwise.
+                return tpis.cancelHighlights();
+            },
+            // intent visualization hook
+            acceptIntent: function (type) {
+                // accept only intents with type "Protected"
+                return (type.startsWith('Protected'));
+            },
+            showIntent: function (info) {
+                $log.debug('^^ topoProtectedIntentsOverlay.showintent() ^^', info);
+                tpis.showProtectedIntent(info);
+            }
+        }
+    };
+
+    // invoke code to register with the overlay service
+    angular.module('ovTopo')
+        .run(['$log', 'TopoOverlayService', 'TopoProtectedIntentsService',
+
+        function (_$log_, _tov_, _tpis_) {
+            $log = _$log_;
+            tov = _tov_;
+            tpis = _tpis_;
+            tov.register(overlay);
+        }]);
+
+}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
index cfd2fb3..724b43c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // injected refs
-    var $log, fs, wss, tov, tps, tts, ns, sus;
+    var $log, fs, wss, tov, tps, tts, ns, sus, tpis;
 
     // api to topoForce
     var api;
@@ -306,9 +306,9 @@
     .factory('TopoSelectService',
         ['$log', 'FnService', 'WebSocketService', 'TopoOverlayService',
             'TopoPanelService', 'TopoTrafficService', 'NavService',
-            'SvgUtilService',
+            'SvgUtilService', 'TopoProtectedIntentsService',
 
-        function (_$log_, _fs_, _wss_, _tov_, _tps_, _tts_, _ns_, _sus_) {
+        function (_$log_, _fs_, _wss_, _tov_, _tps_, _tts_, _ns_, _sus_, _tpis_) {
             $log = _$log_;
             fs = _fs_;
             wss = _wss_;
@@ -317,6 +317,7 @@
             tts = _tts_;
             ns = _ns_;
             sus = _sus_;
+            tpis= _tpis_;
 
             function initSelect(_api_) {
                 api = _api_;
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index 9f85a21..cf1812e 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -190,6 +190,8 @@
     <script src="app/view/topo/topoSprite.js"></script>
     <script src="app/view/topo/topoTraffic.js"></script>
     <script src="app/view/topo/topoTrafficNew.js"></script>
+    <script src="app/view/topo/topoProtectedIntent.js"></script>
+    <script src="app/view/topo/topoProtectedIntentOverlay.js"></script>
     <script src="app/view/topo/topoToolbar.js"></script>
     <script src="app/view/device/device.js"></script>
     <script src="app/view/flow/flow.js"></script>