Adding enhancements to the GUI server-side.
Fixing a few intent-related glitches for the optical use-case.
Fixing lat/lng information in the optical config.

Change-Id: I6a1dd1ee69c2db2f0e351d191627bba468a3c49c
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
index 13f3000..c137b9a 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/AddHostToHostIntentCommand.java
@@ -15,8 +15,6 @@
  */
 package org.onlab.onos.cli.net;
 
-import java.util.List;
-
 import org.apache.karaf.shell.commands.Argument;
 import org.apache.karaf.shell.commands.Command;
 import org.onlab.onos.net.HostId;
@@ -28,6 +26,8 @@
 import org.onlab.onos.net.intent.HostToHostIntent;
 import org.onlab.onos.net.intent.IntentService;
 
+import java.util.List;
+
 /**
  * Installs host-to-host connectivity intent.
  */
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
index 5c513e3..56d9de3 100644
--- a/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
+++ b/cli/src/main/java/org/onlab/onos/cli/net/ConnectivityIntentCommand.java
@@ -20,11 +20,13 @@
 
 import org.apache.karaf.shell.commands.Option;
 import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.Link;
 import org.onlab.onos.net.flow.DefaultTrafficSelector;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.intent.Constraint;
 import org.onlab.onos.net.intent.constraint.BandwidthConstraint;
 import org.onlab.onos.net.intent.constraint.LambdaConstraint;
+import org.onlab.onos.net.intent.constraint.LinkTypeConstraint;
 import org.onlab.onos.net.resource.Bandwidth;
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpPrefix;
@@ -142,6 +144,7 @@
         if (lambda) {
             constraints.add(new LambdaConstraint(null));
         }
+        constraints.add(new LinkTypeConstraint(lambda, Link.Type.OPTICAL));
 
         return constraints;
     }
diff --git a/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
index 24a3bca..e09767a 100644
--- a/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
+++ b/core/api/src/main/java/org/onlab/onos/net/intent/HostToHostIntent.java
@@ -16,12 +16,14 @@
 package org.onlab.onos.net.intent;
 
 import com.google.common.base.MoreObjects;
+import com.google.common.collect.ImmutableList;
 import org.onlab.onos.core.ApplicationId;
 import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.Link;
 import org.onlab.onos.net.flow.TrafficSelector;
 import org.onlab.onos.net.flow.TrafficTreatment;
+import org.onlab.onos.net.intent.constraint.LinkTypeConstraint;
 
-import java.util.Collections;
 import java.util.List;
 
 import static com.google.common.base.Preconditions.checkNotNull;
@@ -47,7 +49,8 @@
     public HostToHostIntent(ApplicationId appId, HostId one, HostId two,
                             TrafficSelector selector,
                             TrafficTreatment treatment) {
-        this(appId, one, two, selector, treatment, Collections.emptyList());
+        this(appId, one, two, selector, treatment,
+             ImmutableList.of(new LinkTypeConstraint(false, Link.Type.OPTICAL)));
     }
 
     /**
diff --git a/tools/test/topos/oe-linear-3.json b/tools/test/topos/oe-linear-3.json
index 728b472..f35cc98 100644
--- a/tools/test/topos/oe-linear-3.json
+++ b/tools/test/topos/oe-linear-3.json
@@ -3,32 +3,32 @@
         {
             "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM1",
-            "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 },
+            "annotations": { "latitude": 37.6, "longitude": -122.3, "optical.regens": 0 },
             "ports": [ { "port": 10, "speed": 100000, "type": "FIBER" }, { "port": 20, "speed": 0, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM2",
-            "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 },
+            "annotations": { "latitude": 37.3, "longitude": -121.9, "optical.regens": 0 },
             "ports": [ { "port": 11, "speed": 100000, "type": "FIBER" }, { "port": 21, "speed": 0, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM3",
-            "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 2 },
+            "annotations": { "latitude": 33.9, "longitude": -118.4, "optical.regens": 2 },
             "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
         },
 
         {
             "uri": "of:0000ffffffff0001", "mac": "ffffffffff0001", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER1",
-            "annotations": { "latitude": 37.6, "longitude": 122.3 },
+            "annotations": { "latitude": 37.6, "longitude": -122.3 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0002", "mac": "ffffffffff0002", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER2",
-            "annotations": { "latitude": 37.3, "longitude": 121.9 },
+            "annotations": { "latitude": 37.3, "longitude": -121.9 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         }
     ],
diff --git a/tools/test/topos/oe-nonlinear-10.json b/tools/test/topos/oe-nonlinear-10.json
index 29086bb..3a48417 100644
--- a/tools/test/topos/oe-nonlinear-10.json
+++ b/tools/test/topos/oe-nonlinear-10.json
@@ -3,7 +3,7 @@
         {
             "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "SFO-W10",
-            "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 },
+            "annotations": { "latitude": 37.6, "longitude": -122.3, "optical.regens": 0 },
             "ports": [ { "port": 10, "speed": 100000, "type": "FIBER" },
             { "port": 20, "speed": 0, "type": "FIBER" },
             { "port": 50, "speed":100000, "type": "FIBER" } ]
@@ -11,7 +11,7 @@
         {
             "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "SJC-W10",
-            "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 },
+            "annotations": { "latitude": 37.3, "longitude": -121.9, "optical.regens": 0 },
             "ports": [ { "port": 20, "speed": 100000, "type": "FIBER" },
             { "port": 30, "speed": 0, "type": "FIBER" },
             { "port": 50, "speed": 0, "type": "FIBER" } ]
@@ -19,7 +19,7 @@
         {
             "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "LAX-W10",
-            "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 0 },
+            "annotations": { "latitude": 33.9, "longitude": -118.4, "optical.regens": 0 },
             "ports": [ { "port": 30, "speed": 0, "type": "FIBER" },
             { "port": 50, "speed": 0, "type": "FIBER" },
             { "port": 20, "speed": 0, "type": "FIBER" } ]
@@ -27,7 +27,7 @@
         {
             "uri": "of:0000ffffffffff04", "mac": "ffffffffffff04", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "SDG-W10",
-            "annotations": { "latitude": 32.8, "longitude": 117.1, "optical.regens": 3 },
+            "annotations": { "latitude": 32.8, "longitude": -117.1, "optical.regens": 3 },
             "ports": [ { "port": 30, "speed": 0, "type": "FIBER" },
             { "port":50, "speed": 0, "type": "FIBER" },
              { "port":20, "speed": 0, "type": "FIBER" }]
@@ -35,7 +35,7 @@
         {
             "uri": "of:0000ffffffffff05", "mac": "ffffffffffff05", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "MSP-M10",
-            "annotations": { "latitude": 44.8, "longitude": 93.1, "optical.regens": 3 },
+            "annotations": { "latitude": 44.8, "longitude": -93.1, "optical.regens": 3 },
             "ports": [ { "port": 20, "speed": 0, "type": "FIBER" },
              { "port": 30, "speed": 0, "type": "FIBER" },
              { "port": 40, "speed": 0, "type": "FIBER" },
@@ -44,7 +44,7 @@
         {
             "uri": "of:0000ffffffffff06", "mac": "ffffffffffff06", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "DFW-M10",
-            "annotations": { "latitude": 32.8, "longitude": 97.1, "optical.regens": 3 },
+            "annotations": { "latitude": 32.8, "longitude": -97.1, "optical.regens": 3 },
             "ports": [ { "port": 10, "speed": 0, "type": "FIBER" },
             { "port": 20, "speed": 0, "type": "FIBER" },
              { "port": 30, "speed": 0, "type": "FIBER" },
@@ -54,7 +54,7 @@
         {
             "uri": "of:0000ffffffffff07", "mac": "ffffffffffff07", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "CHG-N10",
-            "annotations": { "latitude": 41.8, "longitude": 120.1, "optical.regens": 3 },
+            "annotations": { "latitude": 41.8, "longitude": -120.1, "optical.regens": 3 },
             "ports": [ { "port": 10, "speed": 0, "type": "FIBER" },
             { "port": 20, "speed": 0, "type": "FIBER" },
              { "port": 30, "speed": 0, "type": "FIBER" },
@@ -63,7 +63,7 @@
         {
             "uri": "of:0000ffffffffff08", "mac": "ffffffffffff08", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "IAD-M10",
-            "annotations": { "latitude": 38.8, "longitude": 77.1, "optical.regens": 3 },
+            "annotations": { "latitude": 38.8, "longitude": -77.1, "optical.regens": 3 },
             "ports": [ { "port": 20, "speed": 0, "type": "FIBER" },
              { "port": 30, "speed": 0, "type": "FIBER" },
              { "port": 50, "speed": 0, "type": "FIBER" }]
@@ -71,7 +71,7 @@
         {
             "uri": "of:0000ffffffffff09", "mac": "ffffffffffff09", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "JFK-M10",
-            "annotations": { "latitude": 40.8, "longitude": 73.1, "optical.regens": 0 },
+            "annotations": { "latitude": 40.8, "longitude": -73.1, "optical.regens": 0 },
             "ports": [ { "port": 10, "speed": 0, "type": "FIBER" },
             { "port": 20, "speed": 0, "type": "FIBER" },
             { "port": 50, "speed": 0, "type": "FIBER" }]
@@ -79,7 +79,7 @@
         {
             "uri": "of:0000ffffffffff0A", "mac": "ffffffffffff0A", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ATL-S10",
-            "annotations": { "latitude": 33.8, "longitude": 84.1, "optical.regens": 0 },
+            "annotations": { "latitude": 33.8, "longitude": -84.1, "optical.regens": 0 },
             "ports": [ { "port": 10, "speed": 0, "type": "FIBER" },
             { "port": 20, "speed": 0, "type": "FIBER" },
             { "port": 50, "speed": 0, "type": "FIBER" }]
@@ -87,37 +87,37 @@
         {
             "uri": "of:0000ffffffff0001", "mac": "ffffffffff0001", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "SFO-R10",
-            "annotations": { "latitude": 37.6, "longitude": 122.3 },
+            "annotations": { "latitude": 37.6, "longitude": -122.3 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0002", "mac": "ffffffffff0003", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "LAX-R10",
-            "annotations": { "latitude": 33.9, "longitude": 118.4 },
+            "annotations": { "latitude": 33.9, "longitude": -118.4 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0003", "mac": "ffffffffff0004", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "SDG-R10",
-            "annotations": { "latitude": 32.8, "longitude": 117.1 },
+            "annotations": { "latitude": 32.8, "longitude": -117.1 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0004", "mac": "ffffffffff0007", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "CHG-R10",
-            "annotations": { "latitude": 41.8, "longitude": 120.1 },
+            "annotations": { "latitude": 41.8, "longitude": -120.1 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0005", "mac": "ffffffffff0009", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "JFK-R10",
-            "annotations": { "latitude": 40.8, "longitude": 73.1 },
+            "annotations": { "latitude": 40.8, "longitude": -73.1 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0006", "mac": "ffffffffff000A", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ATL-R10",
-            "annotations": { "latitude": 33.8, "longitude": 84.1 },
+            "annotations": { "latitude": 33.8, "longitude": -84.1 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         }
 
diff --git a/tools/test/topos/oe-nonlinear-4.json b/tools/test/topos/oe-nonlinear-4.json
index ac86a01..7082047 100644
--- a/tools/test/topos/oe-nonlinear-4.json
+++ b/tools/test/topos/oe-nonlinear-4.json
@@ -3,37 +3,37 @@
         {
             "uri": "of:0000ffffffffff01", "mac": "ffffffffffff01", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM1",
-            "annotations": { "latitude": 37.6, "longitude": 122.3, "optical.regens": 0 },
-            "ports": [ { "port": 10, "speed": 100000, "type": "FIBER" }, { "port": 20, "speed": 0, "type": "FIBER" }, { "port": 22, "speed": 0, "type": "FIBER" }]
+            "annotations": { "latitude": 37.6, "longitude": -122.3, "optical.regens": 0 },
+            "ports": [ { "port": 10, "speed": 100000, "type": "FIBER" }, { "port": 20, "speed": 0, "type": "FIBER" }, { "port": 22, "speed": 0, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffffff02", "mac": "ffffffffffff02", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM2",
-            "annotations": { "latitude": 37.3, "longitude": 121.9, "optical.regens": 0 },
-            "ports": [ { "port": 11, "speed": 100000, "type": "FIBER" }, { "port": 21, "speed": 0, "type": "FIBER" }, { "port": 22, "speed": 0, "type": "FIBER" }]
+            "annotations": { "latitude": 37.3, "longitude": -121.9, "optical.regens": 0 },
+            "ports": [ { "port": 11, "speed": 100000, "type": "FIBER" }, { "port": 21, "speed": 0, "type": "FIBER" }, { "port": 22, "speed": 0, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffffff03", "mac": "ffffffffffff03", "type": "ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM3",
-            "annotations": { "latitude": 33.9, "longitude": 118.4, "optical.regens": 2 },
-            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" }]
+            "annotations": { "latitude": 33.9, "longitude": -118.4, "optical.regens": 2 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
         },
-	{
+        {
             "uri": "of:0000ffffffffff04", "mac": "ffffffffffff04", "type":"ROADM",
             "mfr": "Linc", "hw": "OE", "sw": "?", "serial": "?", "name": "ROADM4",
-            "annotations": { "latitude": 39.9, "longitude": 119.4, "optical.regens": 2 },
-            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" }]
+            "annotations": { "latitude": 39.9, "longitude": -119.4, "optical.regens": 2 },
+            "ports": [ { "port": 30, "speed": 0, "type": "FIBER" }, { "port": 31, "speed": 0, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0001", "mac": "ffffffffff0001", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER1",
-            "annotations": { "latitude": 37.6, "longitude": 122.3 },
+            "annotations": { "latitude": 37.6, "longitude": -122.3 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         },
         {
             "uri": "of:0000ffffffff0002", "mac": "ffffffffff0002", "type": "SWITCH",
             "mfr": "Linc", "hw": "PK", "sw": "?", "serial": "?", "name": "ROUTER2",
-            "annotations": { "latitude": 37.3, "longitude": 121.9 },
+            "annotations": { "latitude": 37.3, "longitude": -121.9 },
             "ports": [ { "port": 1, "speed": 10000, "type": "COPPER" }, { "port": 2, "speed": 100000, "type": "FIBER" } ]
         }
     ],
@@ -49,3 +49,4 @@
     ]
 
 }
+
diff --git a/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
index 34d5814..30fc51f 100644
--- a/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
+++ b/web/api/src/main/java/org/onlab/onos/rest/ConfigResource.java
@@ -21,6 +21,8 @@
 import org.onlab.onos.net.host.HostProviderRegistry;
 import org.onlab.onos.net.link.LinkProviderRegistry;
 import org.onlab.rest.BaseResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.POST;
@@ -31,6 +33,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 
+import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
+
 /**
  * Resource that acts as an ancillary provider for uploading pre-configured
  * devices, ports and links.
@@ -38,17 +42,24 @@
 @Path("config")
 public class ConfigResource extends BaseResource {
 
+    private static Logger log = LoggerFactory.getLogger(ConfigResource.class);
+
     @POST
     @Path("topology")
     @Consumes(MediaType.APPLICATION_JSON)
     @Produces(MediaType.APPLICATION_JSON)
     public Response topology(InputStream input) throws IOException {
-        ObjectMapper mapper = new ObjectMapper();
-        JsonNode cfg = mapper.readTree(input);
-        new ConfigProvider(cfg, get(DeviceProviderRegistry.class),
-                           get(LinkProviderRegistry.class),
-                           get(HostProviderRegistry.class)).parse();
-        return Response.ok(mapper.createObjectNode().toString()).build();
+        try {
+            ObjectMapper mapper = new ObjectMapper();
+            JsonNode cfg = mapper.readTree(input);
+            new ConfigProvider(cfg, get(DeviceProviderRegistry.class),
+                               get(LinkProviderRegistry.class),
+                               get(HostProviderRegistry.class)).parse();
+            return Response.ok(mapper.createObjectNode().toString()).build();
+        } catch (Exception e) {
+            log.error("Unable to parse topology configuration", e);
+            return Response.status(INTERNAL_SERVER_ERROR).entity(e.toString()).build();
+        }
     }
 
 }
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java
index f747423..e7bc37e 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyMessages.java
@@ -40,7 +40,12 @@
 import org.onlab.onos.net.device.DeviceService;
 import org.onlab.onos.net.host.HostEvent;
 import org.onlab.onos.net.host.HostService;
+import org.onlab.onos.net.intent.ConnectivityIntent;
+import org.onlab.onos.net.intent.Intent;
 import org.onlab.onos.net.intent.IntentService;
+import org.onlab.onos.net.intent.IntentState;
+import org.onlab.onos.net.intent.LinkCollectionIntent;
+import org.onlab.onos.net.intent.PathIntent;
 import org.onlab.onos.net.link.LinkEvent;
 import org.onlab.onos.net.link.LinkService;
 import org.onlab.onos.net.provider.ProviderId;
@@ -50,6 +55,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -350,7 +356,7 @@
                              new Prop("Longitude", annot.value("longitude"))));
     }
 
-    // Produces a path message to the client.
+    // Produces a path payload to the client.
     protected ObjectNode pathMessage(Path path, String type) {
         ObjectNode payload = mapper.createObjectNode();
         ArrayNode links = mapper.createArrayNode();
@@ -362,6 +368,50 @@
         return payload;
     }
 
+
+    // Produces JSON message to trigger traffic visualization
+    protected ObjectNode trafficMessage(Set<Intent> intents, long sid) {
+        ObjectNode payload = mapper.createObjectNode();
+        ArrayNode paths = mapper.createArrayNode();
+        payload.set("paths", paths);
+
+        for (Intent intent : intents) {
+            List<Intent> installables = intentService.getInstallableIntents(intent.id());
+            IntentState state = intentService.getIntentState(intent.id());
+            String type = state == IntentState.FAILED ? "inactive" : "active";
+            for (Intent installable : installables) {
+                if (installable instanceof ConnectivityIntent) {
+                    addPathTraffic(paths, type, (ConnectivityIntent) installable);
+                }
+            }
+        }
+
+        return envelope("showTraffic", sid, payload);
+    }
+
+    // Adds the link segments (path or tree) associated with the specified
+    // connectivity intent
+    protected void addPathTraffic(ArrayNode paths, String type,
+                                  ConnectivityIntent installable) {
+        ObjectNode pathNode = mapper.createObjectNode();
+        ArrayNode linksNode = mapper.createArrayNode();
+
+        Iterable<Link> links;
+        if (installable instanceof PathIntent) {
+            links = ((PathIntent) installable).path().links();
+        } else if (installable instanceof LinkCollectionIntent) {
+            links = ((LinkCollectionIntent) installable).links();
+        } else {
+            return;
+        }
+
+        for (Link link : links) {
+            linksNode.add(compactLinkString(link));
+        }
+        pathNode.put("type", type).set("links", linksNode);
+        paths.add(pathNode);
+    }
+
     // Produces compact string representation of a link.
     private static String compactLinkString(Link link) {
         return String.format(COMPACT, link.src().elementId(), link.src().port(),
diff --git a/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java b/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
index e4bfccd..46bc930 100644
--- a/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
+++ b/web/gui/src/main/java/org/onlab/onos/gui/TopologyWebSocket.java
@@ -15,6 +15,8 @@
  */
 package org.onlab.onos.gui;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.eclipse.jetty.websocket.WebSocket;
 import org.onlab.onos.cluster.ClusterEvent;
@@ -24,6 +26,7 @@
 import org.onlab.onos.core.CoreService;
 import org.onlab.onos.mastership.MastershipEvent;
 import org.onlab.onos.mastership.MastershipListener;
+import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.Device;
 import org.onlab.onos.net.Host;
 import org.onlab.onos.net.HostId;
@@ -38,16 +41,19 @@
 import org.onlab.onos.net.intent.HostToHostIntent;
 import org.onlab.onos.net.intent.Intent;
 import org.onlab.onos.net.intent.IntentEvent;
-import org.onlab.onos.net.intent.IntentId;
 import org.onlab.onos.net.intent.IntentListener;
+import org.onlab.onos.net.intent.MultiPointToSinglePointIntent;
 import org.onlab.onos.net.intent.PathIntent;
+import org.onlab.onos.net.intent.PointToPointIntent;
 import org.onlab.onos.net.link.LinkEvent;
 import org.onlab.onos.net.link.LinkListener;
 import org.onlab.osgi.ServiceDirectory;
 
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
@@ -85,9 +91,10 @@
     private final IntentListener intentListener = new InternalIntentListener();
 
     // Intents that are being monitored for the GUI
-    private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
+    private static Map<Intent, Long> intentsToMonitor = new ConcurrentHashMap<>();
 
     private long lastActive = System.currentTimeMillis();
+    private boolean listenersRemoved = false;
 
     /**
      * Creates a new web-socket for serving data to GUI topology view.
@@ -103,8 +110,8 @@
      * Issues a close on the connection.
      */
     synchronized void close() {
+        removeListeners();
         if (connection.isOpen()) {
-            removeListeners();
             connection.close();
         }
     }
@@ -140,9 +147,7 @@
 
     @Override
     public synchronized void onClose(int closeCode, String message) {
-        if (connection.isOpen()) {
-            removeListeners();
-        }
+        removeListeners();
         log.info("GUI client disconnected");
     }
 
@@ -165,7 +170,7 @@
             } else if (type.equals("requestPath")) {
                 createHostIntent(event);
             } else if (type.equals("requestTraffic")) {
-                sendTraffic(event);
+                requestTraffic(event);
             } else if (type.equals("cancelTraffic")) {
                 cancelTraffic(event);
             }
@@ -217,12 +222,12 @@
     private void requestDetails(ObjectNode event) {
         ObjectNode payload = payload(event);
         String type = string(payload, "class", "unknown");
+        long sid = number(event, "sid");
+
         if (type.equals("device")) {
-            sendMessage(deviceDetails(deviceId(string(payload, "id")),
-                                      number(event, "sid")));
+            sendMessage(deviceDetails(deviceId(string(payload, "id")), sid));
         } else if (type.equals("host")) {
-            sendMessage(hostDetails(hostId(string(payload, "id")),
-                                    number(event, "sid")));
+            sendMessage(hostDetails(hostId(string(payload, "id")), sid));
         }
     }
 
@@ -237,27 +242,137 @@
         HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
                                                            DefaultTrafficSelector.builder().build(),
                                                            DefaultTrafficTreatment.builder().build());
-        intentsToMonitor.put(hostIntent.id(), number(event, "sid"));
+        intentsToMonitor.put(hostIntent, number(event, "sid"));
         intentService.submit(hostIntent);
     }
 
     // Sends traffic message.
-    private void sendTraffic(ObjectNode event) {
+    private void requestTraffic(ObjectNode event) {
         ObjectNode payload = payload(event);
-        long id = number(event, "sid");
-        IntentId intentId = IntentId.valueOf(payload.path("intentId").asLong());
+        long sid = number(event, "sid");
+        Set<Intent> intents = findPathIntents(payload);
 
-        if (payload != null) {
-            payload.put("traffic", true);
-            sendMessage(envelope("showPath", id, payload));
-        } else {
-            sendMessage(warning(id, "No path found"));
+        // Add all those intents to the list of monitored intents & flows.
+        intentsToMonitor.clear();
+        for (Intent intent : intents) {
+            intentsToMonitor.put(intent, sid);
         }
+
+        // Send an initial message to highlight all links of all monitored intents.
+        sendMessage(trafficMessage(intents, sid));
     }
 
     // Cancels sending traffic messages.
     private void cancelTraffic(ObjectNode event) {
-        // TODO: implement this
+        ObjectNode payload = payload(event);
+        long sid = number(event, "sid");
+        Set<Intent> intents = findPathIntents(payload);
+
+        // Remove all those intents from the list of monitored intents & flows.
+        intentsToMonitor.clear(); // TODO: remove when ready
+        for (Intent intent : intents) {
+            intentsToMonitor.remove(intent.id());
+        }
+        sendMessage(trafficMessage(intents, sid));
+    }
+
+    // Finds all path (host-to-host or point-to-point) intents that pertains
+    // to the hosts indicated in the given event payload.
+    private Set<Intent> findPathIntents(ObjectNode payload) {
+        // Get the list of selected hosts.
+        Set<Host> hosts = getHosts((ArrayNode) payload.path("ids"));
+
+        // Derive from this the set of edge connect points.
+        Set<ConnectPoint> edgePoints = getEdgePoints(hosts);
+
+        // Iterate over all intents and produce a set that contains only those
+        // intents that target all selected hosts or derived edge connect points.
+        return getIntents(hosts, edgePoints);
+    }
+
+    // Produces a set of intents that target all selected hosts or connect points.
+    private Set<Intent> getIntents(Set<Host> hosts, Set<ConnectPoint> edgePoints) {
+        Set<Intent> intents = new HashSet<>();
+        for (Intent intent : intentService.getIntents()) {
+            boolean isRelevant = false;
+            if (intent instanceof HostToHostIntent) {
+                isRelevant = isIntentRelevant((HostToHostIntent) intent, hosts);
+            } else if (intent instanceof PointToPointIntent) {
+                isRelevant = isIntentRelevant((PointToPointIntent) intent, edgePoints);
+            } else if (intent instanceof MultiPointToSinglePointIntent) {
+                isRelevant = isIntentRelevant((MultiPointToSinglePointIntent) intent, edgePoints);
+            }
+            // TODO: add other intents, e.g. SinglePointToMultiPointIntent
+
+            if (isRelevant) {
+                intents.add(intent);
+            }
+        }
+        return intents;
+    }
+
+    // Indicates whether the specified intent involves all of the given hosts.
+    private boolean isIntentRelevant(HostToHostIntent intent, Set<Host> hosts) {
+        for (Host host : hosts) {
+            HostId id = host.id();
+            // Bail if intent does not involve this host.
+            if (!id.equals(intent.one()) && !id.equals(intent.two())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // Indicates whether the specified intent involves all of the given edge points.
+    private boolean isIntentRelevant(PointToPointIntent intent,
+                                     Set<ConnectPoint> edgePoints) {
+        for (ConnectPoint point : edgePoints) {
+            // Bail if intent does not involve this edge point.
+            if (!point.equals(intent.egressPoint()) &&
+                    !point.equals(intent.ingressPoint())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // Indicates whether the specified intent involves all of the given edge points.
+    private boolean isIntentRelevant(MultiPointToSinglePointIntent intent,
+                                     Set<ConnectPoint> edgePoints) {
+        for (ConnectPoint point : edgePoints) {
+            // Bail if intent does not involve this edge point.
+            if (!point.equals(intent.egressPoint()) &&
+                    !intent.ingressPoints().contains(point)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    // Produces a set of all host ids listed in the specified JSON array.
+    private Set<Host> getHosts(ArrayNode array) {
+        Set<Host> hosts = new HashSet<>();
+        for (JsonNode node : array) {
+            try {
+                Host host = hostService.getHost(hostId(node.asText()));
+                if (host != null) {
+                    hosts.add(host);
+                }
+            } catch (IllegalArgumentException e) {
+                log.debug("Skipping ID {}", node.asText());
+            }
+        }
+        return hosts;
+    }
+
+    // Produces a set of edge points from the specified set of hosts.
+    private Set<ConnectPoint> getEdgePoints(Set<Host> hosts) {
+        Set<ConnectPoint> edgePoints = new HashSet<>();
+        for (Host host : hosts) {
+            edgePoints.add(host.location());
+        }
+        return edgePoints;
     }
 
 
@@ -272,13 +387,16 @@
     }
 
     // Removes all internal listeners.
-    private void removeListeners() {
-        clusterService.removeListener(clusterListener);
-        deviceService.removeListener(deviceListener);
-        linkService.removeListener(linkListener);
-        hostService.removeListener(hostListener);
-        mastershipService.removeListener(mastershipListener);
-        intentService.removeListener(intentListener);
+    private synchronized void removeListeners() {
+        if (!listenersRemoved) {
+            listenersRemoved = true;
+            clusterService.removeListener(clusterListener);
+            deviceService.removeListener(deviceListener);
+            linkService.removeListener(linkListener);
+            hostService.removeListener(hostListener);
+            mastershipService.removeListener(mastershipListener);
+            intentService.removeListener(intentListener);
+        }
     }
 
     // Cluster event listener.
@@ -327,7 +445,7 @@
         @Override
         public void event(IntentEvent event) {
             Intent intent = event.subject();
-            Long sid = intentsToMonitor.get(intent.id());
+            Long sid = intentsToMonitor.get(intent);
             if (sid != null) {
                 List<Intent> installable = intentService.getInstallableIntents(intent.id());
                 if (installable != null && !installable.isEmpty()) {
diff --git a/web/gui/src/main/webapp/topo2.js b/web/gui/src/main/webapp/topo2.js
index 8934ec3..f93595f 100644
--- a/web/gui/src/main/webapp/topo2.js
+++ b/web/gui/src/main/webapp/topo2.js
@@ -130,8 +130,8 @@
         U: unpin,
 
         W: requestTraffic,  // bag of selections
-        Z: requestPath,     // host-to-host intent (and monitor)
-        X: cancelMonitor
+        X: cancelTraffic,
+        Z: requestPath      // host-to-host intent (and monitor)
     };
 
     // state variables
@@ -496,7 +496,23 @@
     }
 
     function showTraffic(data) {
-        network.view.alert("showTraffic() -- TODO")
+        fnTrace('showTraffic', data.payload.id);
+        data.payload.paths.forEach(function () {
+            var links = data.payload.links,
+                s = [ data.event + "\n" + links.length ];
+            links.forEach(function (d, i) {
+                s.push(d);
+            });
+            network.view.alert(s.join('\n'));
+
+            links.forEach(function (d, i) {
+                var link = network.lookup[d];
+                if (link) {
+                    link.el.classed('showPath', true);
+                }
+            });
+        });
+        //network.view.alert("showTraffic() -- TODO")
     }
 
     // ...............................
@@ -566,9 +582,9 @@
         }
     }
 
-    function cancelMonitor() {
+    function cancelTraffic() {
         // FIXME: from where do we get the intent id(s) to send to the server?
-        sendMessage('cancelMonitor', {
+        sendMessage('cancelTraffic', {
             ids: ["need_the_intent_id"]
         });
     }