Bug fix: Topology WebSocket timeout if there are no events to transmit

Apparently, if there are no topology-related events to transmit to
the client, the WebSocket will automatically timeout after several minutes.

The solution is to use the WebSocket embedded PING-PONG mechanism.
Implementation-wise, if there are no events to transmit for 30 seconds,
ONOS will transmit a PING message. If three PING messages are transmitted,
and there is no PONG, then the WebSocket is closed. The total
timeout time is approximately 30x(3+1) = 120 seconds.
Note that the PONG message is automatically transmitted by the Web browsers,
so no code change is required to the ONOS GUI.

This fixes bug ONOS-1932

Also, updated websocket-api artifact in pom.xml from 1.0 to 1.1

Change-Id: I474e04e81a6f613774bb987f17a6a0b5d3afa99d
diff --git a/pom.xml b/pom.xml
index 6b0b2a1..9ccd5d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -527,7 +527,7 @@
     <dependency>
       <groupId>javax.websocket</groupId>
       <artifactId>javax.websocket-api</artifactId>
-      <version>1.0</version>
+      <version>1.1</version>
     </dependency>
     <dependency>
       <groupId>net.sf.json-lib</groupId>
diff --git a/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java b/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java
index 9d9ed6f..fe365d9 100644
--- a/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java
+++ b/src/main/java/net/onrc/onos/apps/websocket/TopologyWebSocket.java
@@ -6,8 +6,10 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
 
 import javax.websocket.CloseReason;
 import javax.websocket.EndpointConfig;
@@ -58,6 +60,10 @@
         new LinkedBlockingQueue<>();
     private Session socketSession;
     private boolean isOpen = false;
+    // Ping-related state
+    private static final int PING_INTERVAL_SEC = 30;   // Ping every 30 secs
+    private static final int MAX_MISSING_PING = 3;
+    private int missingPing = 0;        // Pings to the client without a pong
 
     /**
      * Shutdown the socket.
@@ -76,7 +82,7 @@
      */
     @Override
     public void topologyEvents(TopologyEvents topologyEvents) {
-        // The topologyEvents object is a deep copy so we can add it as-is
+        // The topologyEvents object is immutable, so we can add it as-is
         this.topologyEventsQueue.add(topologyEvents);
     }
 
@@ -89,18 +95,39 @@
         ObjectMapper mapper = new ObjectMapper();
 
         //
-        // The main loop for sending events to the clients
+        // The main loop for sending events to the clients.
+        //
+        // If there are no events, we send periodic PING messages to discover
+        // unreachable clients.
         //
         while (this.isOpen && (!this.isInterrupted())) {
             String eventsJson = null;
             try {
-                TopologyEvents events = topologyEventsQueue.take();
-                eventsJson = mapper.writeValueAsString(events);
-                if (eventsJson != null) {
+                TopologyEvents events =
+                    topologyEventsQueue.poll(PING_INTERVAL_SEC,
+                                             TimeUnit.SECONDS);
+                if (events != null) {
+                    // Send the event
+                    eventsJson = mapper.writeValueAsString(events);
                     socketSession.getBasicRemote().sendText(eventsJson);
+                    continue;
+                }
+                // Send a PING message
+                missingPing++;
+                if (missingPing > MAX_MISSING_PING) {
+                    // Timeout
+                    log.debug("WebSocket session timeout");
+                    shutdown();
+                } else {
+                    String msg = "PING(TopologyWebsocket)";
+                    ByteBuffer pingBuffer =
+                        ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));
+                    socketSession.getBasicRemote().sendPing(pingBuffer);
                 }
             } catch (IOException e) {
                 log.debug("Exception sending TopologyWebSocket events: ", e);
+            } catch (InterruptedException e) {
+                log.debug("TopologyWebSocket interrupted while waiting: ", e);
             } catch (Exception exception) {
                 log.debug("Exception processing TopologyWebSocket events: ",
                           exception);
@@ -174,8 +201,9 @@
      */
     @OnMessage
     public void onPongMessage(Session session, PongMessage msg) {
-        log.debug("WebSocket Pong message received: {}",
-                  msg.getApplicationData());
+        log.trace("WebSocket Pong message received for session: {}",
+                  session.getId());
+        missingPing = 0;
     }
 
     /**