Added top-level location object per Simon/Paul spec.
Fixed an NPE in mastership when running optical demo.

Change-Id: I038000d1237b4150db0c722fa96dd9541d83e44e
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 0b54dd8..d68e8fa 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
@@ -24,6 +24,7 @@
 import org.onlab.onos.cluster.ControllerNode;
 import org.onlab.onos.cluster.NodeId;
 import org.onlab.onos.mastership.MastershipService;
+import org.onlab.onos.net.Annotated;
 import org.onlab.onos.net.Annotations;
 import org.onlab.onos.net.ConnectPoint;
 import org.onlab.onos.net.DefaultEdgeLink;
@@ -45,6 +46,8 @@
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.osgi.ServiceDirectory;
 import org.onlab.packet.IpAddress;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Iterator;
 import java.util.Map;
@@ -68,6 +71,8 @@
  */
 public abstract class TopologyMessages {
 
+    protected static final Logger log = LoggerFactory.getLogger(TopologyMessages.class);
+
     private static final ProviderId PID = new ProviderId("core", "org.onlab.onos.core", true);
     private static final String COMPACT = "%s/%s-%s/%s";
 
@@ -195,7 +200,7 @@
                 .put("id", device.id().toString())
                 .put("type", device.type().toString().toLowerCase())
                 .put("online", deviceService.isAvailable(device.id()))
-                .put("master", mastershipService.getMasterFor(device.id()).toString());
+                .put("master", master(device.id()));
 
         // Generate labels: id, chassis id, no-label, optional-name
         ArrayNode labels = mapper.createArrayNode();
@@ -207,6 +212,7 @@
         // Add labels, props and stuff the payload into envelope.
         payload.set("labels", labels);
         payload.set("props", props(device.annotations()));
+        addGeoLocation(device, payload);
         addMetaUi(device.id().toString(), payload);
 
         String type = (event.type() == DEVICE_ADDED) ? "addDevice" :
@@ -220,6 +226,7 @@
         ObjectNode payload = mapper.createObjectNode()
                 .put("id", compactLinkString(link))
                 .put("type", link.type().toString().toLowerCase())
+                .put("online", true) // TODO: add link state field
                 .put("linkWidth", 2)
                 .put("src", link.src().deviceId().toString())
                 .put("srcPort", link.src().port().toString())
@@ -237,10 +244,11 @@
                 .put("id", host.id().toString())
                 .put("ingress", compactLinkString(edgeLink(host, true)))
                 .put("egress", compactLinkString(edgeLink(host, false)));
-        payload.set("cp", location(mapper, host.location()));
+        payload.set("cp", hostConnect(mapper, host.location()));
         payload.set("labels", labels(mapper, ip(host.ipAddresses()),
                                      host.mac().toString()));
         payload.set("props", props(host.annotations()));
+        addGeoLocation(host, payload);
         addMetaUi(host.id().toString(), payload);
 
         String type = (event.type() == HOST_ADDED) ? "addHost" :
@@ -249,7 +257,7 @@
     }
 
     // Encodes the specified host location into a JSON object.
-    private ObjectNode location(ObjectMapper mapper, HostLocation location) {
+    private ObjectNode hostConnect(ObjectMapper mapper, HostLocation location) {
         return mapper.createObjectNode()
                 .put("device", location.deviceId().toString())
                 .put("port", location.port().toLong());
@@ -264,6 +272,12 @@
         return json;
     }
 
+    // Returns the name of the master node for the specified device id.
+    private String master(DeviceId deviceId) {
+        NodeId master = mastershipService.getMasterFor(deviceId);
+        return master != null ? master.toString() : "";
+    }
+
     // Generates an edge link from the specified host location.
     private EdgeLink edgeLink(Host host, boolean ingress) {
         return new DefaultEdgeLink(PID, new ConnectPoint(host.id(), portNumber(0)),
@@ -278,6 +292,24 @@
         }
     }
 
+    // Adds a geo location JSON to the specified payload object.
+    private void addGeoLocation(Annotated annotated, ObjectNode payload) {
+        Annotations annotations = annotated.annotations();
+        String slat = annotations.value("latitude");
+        String slng = annotations.value("longitude");
+        try {
+            if (slat != null && slng != null && !slat.isEmpty() && !slng.isEmpty()) {
+                double lat = Double.parseDouble(slat);
+                double lng = Double.parseDouble(slng);
+                ObjectNode loc = mapper.createObjectNode()
+                        .put("type", "latlng").put("lat", lat).put("lng", lng);
+                payload.set("location", loc);
+            }
+        } catch (NumberFormatException e) {
+            log.warn("Invalid geo data latitude={}; longiture={}", slat, slng);
+        }
+    }
+
     // Updates meta UI information for the specified object.
     protected void updateMetaUi(ObjectNode event) {
         ObjectNode payload = payload(event);
@@ -289,7 +321,6 @@
         Device device = deviceService.getDevice(deviceId);
         Annotations annot = device.annotations();
         int portCount = deviceService.getPorts(deviceId).size();
-        NodeId master = mastershipService.getMasterFor(device.id());
         return envelope("showDetails", sid,
                         json(deviceId.toString(),
                              device.type().toString().toLowerCase(),
@@ -303,7 +334,7 @@
                              new Prop("Longitude", annot.value("longitude")),
                              new Prop("Ports", Integer.toString(portCount)),
                              new Separator(),
-                             new Prop("Master", master.toString())));
+                             new Prop("Master", master(deviceId))));
     }
 
     // Returns host details response.
@@ -321,14 +352,14 @@
 
 
     // Produces a path message to the client.
-    protected ObjectNode pathMessage(Path path) {
+    protected ObjectNode pathMessage(Path path, String type) {
         ObjectNode payload = mapper.createObjectNode();
         ArrayNode links = mapper.createArrayNode();
         for (Link link : path.links()) {
             links.add(compactLinkString(link));
         }
 
-        payload.set("links", links);
+        payload.put("type", type).set("links", links);
         return payload;
     }
 
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 83e54b4..212f14d 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
@@ -122,7 +122,7 @@
                 cancelTraffic(event);
             }
         } catch (Exception e) {
-            System.out.println("WTF?! " + data);
+            log.warn("Unable to parse GUI request {} due to {}", data, e);
             e.printStackTrace();
         }
     }
@@ -282,7 +282,8 @@
                 if (installable != null && !installable.isEmpty()) {
                     PathIntent pathIntent = (PathIntent) installable.iterator().next();
                     Path path = pathIntent.path();
-                    ObjectNode payload = pathMessage(path).put("intentId", intent.id().toString());
+                    ObjectNode payload = pathMessage(path, "host")
+                            .put("intentId", intent.id().toString());
                     sendMessage(envelope("showPath", sid, payload));
                 }
             }
diff --git a/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json b/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json
index 8656a90..fa7ae6e 100644
--- a/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json
+++ b/web/gui/src/main/webapp/json/ev/simple/ev_1_onos.json
@@ -4,6 +4,11 @@
     "id": "of:0000ffffffff0008",
     "type": "switch",
     "online": false,
+    "location": {
+      "type": "latlng",
+      "lat": 37.6,
+      "lng": 122.3
+    },
     "labels": [
       "0000ffffffff0008",
       "FF:FF:FF:FF:00:08",
diff --git a/web/gui/src/main/webapp/json/of_0000000000000001.json b/web/gui/src/main/webapp/json/of_0000000000000001.json
index 719af80..1f5c8e9 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000001.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000001.json
@@ -3,6 +3,11 @@
     "id": "of:0000000000000001",
     "type": "roadm",
     "propOrder": [ "name", "type", "-", "dpid", "latitude", "longitude", "allowed" ],
+    "location": {
+        "type": "latlng",
+        "lat": 37.6,
+        "lng": 122.3
+    },
     "props": {
         "allowed": true,
         "latitude": 37.6,
diff --git a/web/gui/src/main/webapp/json/of_0000000000000002.json b/web/gui/src/main/webapp/json/of_0000000000000002.json
index a6b0e7c..87fd1f2 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000002.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000002.json
@@ -3,6 +3,11 @@
     "id": "of:0000000000000002",
     "type": "switch",
     "propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
+    "location": {
+        "type": "latlng",
+        "lat": 37.6,
+        "lng": 122.3
+    },
     "props": {
         "allowed": true,
         "latitude": 37.3,
diff --git a/web/gui/src/main/webapp/json/of_0000000000000003.json b/web/gui/src/main/webapp/json/of_0000000000000003.json
index 9fd2790..1315961 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000003.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000003.json
@@ -3,6 +3,11 @@
     "id": "of:0000000000000003",
     "type": "switch",
     "propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
+    "location": {
+        "type": "latlng",
+        "lat": 33.9,
+        "lng": 118.4
+    },
     "props": {
         "allowed": true,
         "latitude": 33.9,
diff --git a/web/gui/src/main/webapp/json/of_0000000000000004.json b/web/gui/src/main/webapp/json/of_0000000000000004.json
index f3f2132..ba243baf 100644
--- a/web/gui/src/main/webapp/json/of_0000000000000004.json
+++ b/web/gui/src/main/webapp/json/of_0000000000000004.json
@@ -3,6 +3,11 @@
     "id": "of:0000000000000004",
     "type": "switch",
     "propOrder": [ "name", "type", "dpid", "latitude", "longitude", "allowed" ],
+    "location": {
+        "type": "latlng",
+        "lat": 32.8,
+        "lng": 117.1
+    },
     "props": {
         "allowed": true,
         "latitude": 32.8,