Deprecating old web-socket stuff and adding ability for client-side message handler registration. Failover still to be done and same for the async hooks.

Change-Id: I6029c91eb1a04e01401e495b9673ddaea728e215
diff --git a/web/gui/src/main/webapp/WEB-INF/web.xml b/web/gui/src/main/webapp/WEB-INF/web.xml
index 511ceac..5371354 100644
--- a/web/gui/src/main/webapp/WEB-INF/web.xml
+++ b/web/gui/src/main/webapp/WEB-INF/web.xml
@@ -151,12 +151,24 @@
 
     <servlet>
         <servlet-name>Web Socket Service</servlet-name>
-        <servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
+        <servlet-class>org.onosproject.ui.impl.UiWebSocketServlet</servlet-class>
         <load-on-startup>2</load-on-startup>
     </servlet>
 
     <servlet-mapping>
         <servlet-name>Web Socket Service</servlet-name>
+        <url-pattern>/websock/*</url-pattern>
+    </servlet-mapping>
+
+
+    <servlet>
+        <servlet-name>Legacy Web Socket Service</servlet-name>
+        <servlet-class>org.onosproject.ui.impl.GuiWebSocketServlet</servlet-class>
+        <load-on-startup>2</load-on-startup>
+    </servlet>
+
+    <servlet-mapping>
+        <servlet-name>Legacy Web Socket Service</servlet-name>
         <url-pattern>/ws/*</url-pattern>
     </servlet-mapping>
 
diff --git a/web/gui/src/main/webapp/app/fw/remote/urlfn.js b/web/gui/src/main/webapp/app/fw/remote/urlfn.js
index fe43267..c2addef 100644
--- a/web/gui/src/main/webapp/app/fw/remote/urlfn.js
+++ b/web/gui/src/main/webapp/app/fw/remote/urlfn.js
@@ -22,7 +22,7 @@
 
     var uiContext = '/onos/ui/',
         rsSuffix = uiContext + 'rs/',
-        wsSuffix = uiContext + 'ws/';
+        wsSuffix = uiContext + 'websock/';
 
     angular.module('onosRemote')
         .factory('UrlFnService', ['$location', function ($loc) {
diff --git a/web/gui/src/main/webapp/app/fw/remote/websocket.js b/web/gui/src/main/webapp/app/fw/remote/websocket.js
index abe6025..3229250 100644
--- a/web/gui/src/main/webapp/app/fw/remote/websocket.js
+++ b/web/gui/src/main/webapp/app/fw/remote/websocket.js
@@ -20,62 +20,105 @@
 (function () {
     'use strict';
 
-    var fs;
+    // injected refs
+    var fs, $log;
 
-    function fnOpen(f) {
-        // wrap the onOpen function; we will handle any housekeeping here...
-        if (!fs.isF(f)) {
-            return null;
-        }
+    // internal state
+    var ws, sws, sid = 0,
+        handlers = {};
 
-        return function (openEvent) {
-            // NOTE: nothing worth passing to the caller?
-            f();
-        };
+    function resetSid() {
+        sid = 0;
     }
 
-    function fnMessage(f) {
-        // wrap the onMessage function; we will attempt to decode the
-        // message event payload as JSON and pass that in...
-        if (!fs.isF(f)) {
-            return null;
-        }
+    // Binds the specified message handlers.
+    function bindHandlers(handlerMap) {
+        var m = d3.map(handlerMap),
+            dups = [];
 
-        return function (msgEvent) {
-            var ev;
-            try {
-                ev = JSON.parse(msgEvent.data);
-            } catch (e) {
-                ev = {
-                    error: 'Failed to parse JSON',
-                    e: e
-                };
+        m.forEach(function (key, value) {
+            var fn = fs.isF(value[key]);
+            if (!fn) {
+                $log.warn(key + ' binding not a function on ' + value);
+                return;
             }
-            f(ev);
-        };
+
+            if (handlers[key]) {
+                dups.push(key);
+            } else {
+                handlers[key] = fn;
+            }
+        });
+        if (dups.length) {
+            $log.warn('duplicate bindings ignored:', dups);
+        }
     }
 
-    function fnClose(f) {
-        // wrap the onClose function; we will handle any parameters to the
-        // close event here...
-        if (!fs.isF(f)) {
-            return null;
-        }
+    // Unbinds the specified message handlers.
+    function unbindHandlers(handlerMap) {
+        var m = d3.map(handlerMap);
+        m.forEach(function (key) {
+            delete handlers[key];
+        });
+    }
 
-        return function (closeEvent) {
-            // NOTE: only seen {reason == ""} so far, nevertheless...
-            f(closeEvent.reason);
-        };
+    // Formulates an event message and sends it via the shared web-socket.
+    function sendEvent(evType, payload) {
+        var p = payload || {};
+        if (sws) {
+            $log.debug(' *Tx* >> ', evType, payload);
+            sws.send({
+                event: evType,
+                sid: ++sid,
+                payload: p
+            });
+        } else {
+            $log.warn('sendEvent: no websocket open:', evType, payload);
+        }
+    }
+
+
+    // Handles the specified message using handler bindings.
+    function handleMessage(msgEvent) {
+        var ev;
+        try {
+            ev = JSON.parse(msgEvent.data);
+            $log.debug(' *Rx* >> ', ev.event, ev.payload);
+            dispatchToHandler(ev);
+        } catch (e) {
+            $log.error('message is not valid JSON', msgEvent);
+        }
+    }
+
+    // Dispatches the message to the appropriate handler.
+    function dispatchToHandler(event) {
+        var handler = handlers[event.event];
+        if (handler) {
+            handler(event.payload);
+        } else {
+            $log.warn('unhandled event:', event);
+        }
+    }
+
+    function handleOpen() {
+        $log.info('web socket open');
+        // FIXME: implement calling external hooks
+    }
+
+    function handleClose() {
+        $log.info('web socket closed');
+        // FIXME: implement reconnect logic
     }
 
     angular.module('onosRemote')
     .factory('WebSocketService',
             ['$log', '$location', 'UrlFnService', 'FnService',
 
-        function ($log, $loc, ufs, _fs_) {
+        function (_$log_, $loc, ufs, _fs_) {
             fs = _fs_;
+            $log = _$log_;
 
-            // creates a web socket for the given path, returning a "handle".
+            // Creates a web socket for the given path, returning a "handle".
             // opts contains the event handler callbacks, etc.
             function createWebSocket(path, opts) {
                 var o = opts || {},
@@ -85,8 +128,7 @@
                         meta: { path: fullUrl, ws: null },
                         send: send,
                         close: close
-                    },
-                    ws;
+                    };
 
                 try {
                     ws = new WebSocket(fullUrl);
@@ -97,23 +139,21 @@
                 $log.debug('Attempting to open websocket to: ' + fullUrl);
 
                 if (ws) {
-                    ws.onopen = fnOpen(o.onOpen);
-                    ws.onmessage = fnMessage(o.onMessage);
-                    ws.onclose = fnClose(o.onClose);
+                    ws.onopen = handleOpen;
+                    ws.onmessage = handleMessage;
+                    ws.onclose = handleClose;
                 }
 
-                // messages are expected to be event objects..
+                // Sends a formulated event message via the backing web-socket.
                 function send(ev) {
-                    if (ev) {
-                        if (ws) {
-                            ws.send(JSON.stringify(ev));
-                        } else {
-                            $log.warn('ws.send() no web socket open!',
-                                fullUrl, ev);
-                        }
+                    if (ev && ws) {
+                        ws.send(JSON.stringify(ev));
+                    } else if (!ws) {
+                        $log.warn('ws.send() no web socket open!', fullUrl, ev);
                     }
                 }
 
+                // Closes the backing web-socket.
                 function close() {
                     if (ws) {
                         ws.close();
@@ -122,11 +162,16 @@
                     }
                 }
 
+                sws = api; // Make the shared web-socket accessible
                 return api;
             }
 
             return {
-                createWebSocket: createWebSocket
+                resetSid: resetSid,
+                createWebSocket: createWebSocket,
+                bindHandlers: bindHandlers,
+                unbindHandlers: unbindHandlers,
+                sendEvent: sendEvent
             };
     }]);
 
diff --git a/web/gui/src/main/webapp/app/fw/remote/wsevent.js b/web/gui/src/main/webapp/app/fw/remote/wsevent.js
index bf04b86..02d71b5 100644
--- a/web/gui/src/main/webapp/app/fw/remote/wsevent.js
+++ b/web/gui/src/main/webapp/app/fw/remote/wsevent.js
@@ -15,6 +15,7 @@
  */
 
 /*
+ DEPRECATED: to be deleted
  ONOS GUI -- Remote -- Web Socket Event Service
  */
 (function () {
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 4463936..3dd8d6e 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -263,7 +263,7 @@
             // Cleanup on destroyed scope..
             $scope.$on('$destroy', function () {
                 $log.log('OvTopoCtrl is saying Buh-Bye!');
-                tes.closeSock();
+                tes.stop();
                 tps.destroyPanels();
                 tis.destroyInst();
                 tfs.destroyForce();
@@ -291,7 +291,7 @@
             tfs.initForce(svg, forceG, uplink, dim);
             tis.initInst({ showMastership: tfs.showMastership });
             tps.initPanels({ sendEvent: tes.sendEvent });
-            tes.openSock();
+            tes.start();
 
             $log.log('OvTopoCtrl has been created');
         }]);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoEvent.js b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
index 099ba4e..10b6909 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoEvent.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
@@ -27,15 +27,15 @@
     'use strict';
 
     // injected refs
-    var $log, wss, wes, vs, tps, tis, tfs, tss, tts;
+    var $log, vs, wss, tps, tis, tfs, tss, tts;
 
     // internal state
-    var wsock, evApis;
+    var handlers;
 
     // ==========================
 
-    function bindApis() {
-        evApis = {
+    function createHandlers() {
+        handlers = {
             showSummary: tps,
 
             showDetails: tss,
@@ -58,103 +58,43 @@
         };
     }
 
-    var nilApi = {},
-        dispatcher = {
-            handleEvent: function (ev) {
-                var eid = ev.event,
-                    api = evApis[eid] || nilApi,
-                    eh = api[eid];
-
-                if (eh) {
-                    $log.debug(' << *Rx* ', eid, ev.payload);
-                    eh(ev.payload);
-                } else {
-                    $log.warn('Unknown event (ignored):', ev);
-                }
-            },
-
-            sendEvent: function (evType, payload) {
-                if (wsock) {
-                    $log.debug(' *Tx* >> ', evType, payload);
-                    wes.sendEvent(wsock, evType, payload);
-                } else {
-                    $log.warn('sendEvent: no websocket open:', evType, payload);
-                }
-            }
-        };
-
-    // ===  Web Socket functions ===
-
-    function onWsOpen() {
-        $log.debug('web socket opened...');
-        // start by requesting periodic summary data...
-        dispatcher.sendEvent('requestSummary');
-        vs.hide();
-    }
-
-    function onWsMessage(ev) {
-        dispatcher.handleEvent(ev);
-    }
-
-    function onWsClose(reason) {
-        $log.log('web socket closed; reason=', reason);
-        wsock = null;
-        vs.lostServer('OvTopoCtrl', [
-            'Oops!',
-            'Web-socket connection to server closed...',
-            'Try refreshing the page.'
-        ]);
-    }
-
-    // ==========================
+    var nilApi = {};
 
     angular.module('ovTopo')
     .factory('TopoEventService',
-        ['$log', '$location', 'WebSocketService', 'WsEventService', 'VeilService',
+        ['$log', '$location', 'VeilService', 'WebSocketService',
             'TopoPanelService', 'TopoInstService', 'TopoForceService',
             'TopoSelectService', 'TopoTrafficService',
 
-        function (_$log_, $loc, _wss_, _wes_, _vs_,
-                  _tps_, _tis_, _tfs_, _tss_, _tts_) {
+        function (_$log_, $loc, _vs_, _wss_, _tps_, _tis_, _tfs_, _tss_, _tts_) {
             $log = _$log_;
-            wss = _wss_;
-            wes = _wes_;
             vs = _vs_;
+            wss = _wss_;
             tps = _tps_;
             tis = _tis_;
             tfs = _tfs_;
             tss = _tss_;
             tts = _tts_;
 
-            bindApis();
+            createHandlers();
 
-            // TODO: handle "guiSuccessor" functionality (replace host)
-            // TODO: implement retry on close functionality
-
-            function openSock() {
-                wsock = wss.createWebSocket('topology', {
-                    onOpen: onWsOpen,
-                    onMessage: onWsMessage,
-                    onClose: onWsClose,
-                    wsport: $loc.search().wsport
-                });
-                $log.debug('web socket opened:', wsock);
+            // FIXME: need to handle async socket open to avoid race
+            function start() {
+                wss.bindHandlers(handlers);
+                wss.sendEvent('topoStart');
+                $log.debug('topo comms started');
             }
 
-            function closeSock() {
-                var path;
-                if (wsock) {
-                    path = wsock.meta.path;
-                    wsock.close();
-                    wsock = null;
-                    $log.debug('web socket closed. path:', path);
-                }
+            function stop() {
+                wss.unbindHandlers();
+                wss.sendEvent('topoStop');
+                $log.debug('topo comms stopped');
             }
 
             return {
-                openSock: openSock,
-                closeSock: closeSock,
-                sendEvent: dispatcher.sendEvent
+                start: start,
+                stop: stop,
+                sendEvent: wss.sendEvent
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/onos.js b/web/gui/src/main/webapp/onos.js
index 0f049e9..97f3d06 100644
--- a/web/gui/src/main/webapp/onos.js
+++ b/web/gui/src/main/webapp/onos.js
@@ -64,10 +64,10 @@
         .controller('OnosCtrl', [
             '$log', '$route', '$routeParams', '$location',
             'KeyService', 'ThemeService', 'GlyphService', 'PanelService',
-            'FlashService', 'QuickHelpService',
+            'FlashService', 'QuickHelpService', 'WebSocketService',
 
             function ($log, $route, $routeParams, $location,
-                      ks, ts, gs, ps, flash, qhs) {
+                      ks, ts, gs, ps, flash, qhs, wss) {
                 var self = this;
 
                 self.$route = $route;
@@ -84,6 +84,13 @@
                 flash.initFlash();
                 qhs.initQuickHelp();
 
+                // TODO: register handlers for initial messages: instances, settings, etc.
+
+                // TODO: opts?
+                wss.createWebSocket('core', {
+                    wsport: $location.search().wsport
+                });
+
                 $log.log('OnosCtrl has been created');
 
                 $log.debug('route: ', self.$route);