ONOS-1235 Enhanced UI extension mechanism to provide message handler factory and took a first cut at the core UiWebSocket mechanism.
Change-Id: Iaad080c5371c3aa5e24a23489b1679d373ec0720
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 3604919..3b9156d 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
@@ -24,6 +24,7 @@
import org.apache.felix.scr.annotations.Service;
import org.onosproject.ui.UiExtension;
import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
import org.onosproject.ui.UiView;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,12 +51,18 @@
private final Map<String, UiExtension> views = Maps.newHashMap();
// Core views & core extension
- private final List<UiView> coreViews = of(new UiView("sample", "Sample"),
- new UiView("topo", "Topology View"),
- new UiView("device", "Devices"));
+ private final UiExtension core = createCoreExtension();
- private final UiExtension core = new UiExtension(coreViews, "core",
- getClass().getClassLoader());
+
+ // Creates core UI extension
+ private static UiExtension createCoreExtension() {
+ List<UiView> coreViews = of(new UiView("sample", "Sample"),
+ new UiView("topo", "Topology View"),
+ new UiView("device", "Devices"));
+ UiMessageHandlerFactory messageHandlerFactory = null;
+ return new UiExtension(coreViews, messageHandlerFactory, "core",
+ UiExtensionManager.class.getClassLoader());
+ }
@Activate
public void activate() {
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
new file mode 100644
index 0000000..9b11b42
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocket.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2015 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.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.eclipse.jetty.websocket.WebSocket;
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.ui.UiConnection;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Web socket capable of interacting with the GUI.
+ */
+public class UiWebSocket
+ implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl {
+
+ private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
+
+ private static final long MAX_AGE_MS = 15000;
+
+ private static final byte PING = 0x9;
+ private static final byte PONG = 0xA;
+ private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
+
+ private final ServiceDirectory directory;
+
+ private Connection connection;
+ private FrameConnection control;
+
+ private final ObjectMapper mapper = new ObjectMapper();
+
+ private long lastActive = System.currentTimeMillis();
+
+ private Map<String, UiMessageHandler> handlers;
+
+ /**
+ * Creates a new web-socket for serving data to GUI.
+ *
+ * @param directory service directory
+ */
+ public UiWebSocket(ServiceDirectory directory) {
+ this.directory = directory;
+ }
+
+ /**
+ * Issues a close on the connection.
+ */
+ synchronized void close() {
+ destroyHandlers();
+ if (connection.isOpen()) {
+ connection.close();
+ }
+ }
+
+ /**
+ * Indicates if this connection is idle.
+ *
+ * @return true if idle or closed
+ */
+ synchronized boolean isIdle() {
+ boolean idle = (System.currentTimeMillis() - lastActive) > MAX_AGE_MS;
+ if (idle || (connection != null && !connection.isOpen())) {
+ return true;
+ } else if (connection != null) {
+ try {
+ control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
+ } catch (IOException e) {
+ log.warn("Unable to send ping message due to: ", e);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onOpen(Connection connection) {
+ log.info("GUI client connected");
+ this.connection = connection;
+ this.control = (FrameConnection) connection;
+ createHandlers();
+ }
+
+ @Override
+ public synchronized void onClose(int closeCode, String message) {
+ destroyHandlers();
+ log.info("GUI client disconnected");
+ }
+
+ @Override
+ public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
+ lastActive = System.currentTimeMillis();
+ return true;
+ }
+
+ @Override
+ public void onMessage(String data) {
+ lastActive = System.currentTimeMillis();
+ try {
+ ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
+ String type = message.path("type").asText("unknown");
+ UiMessageHandler handler = handlers.get(type);
+ if (handler != null) {
+ handler.process(message);
+ } else {
+ log.warn("No GUI message handler for type {}", type);
+ }
+ } catch (Exception e) {
+ log.warn("Unable to parse GUI message {} due to {}", data, e);
+ log.debug("Boom!!!", e);
+ }
+ }
+
+ @Override
+ public void sendMessage(ObjectNode message) {
+ try {
+ if (connection.isOpen()) {
+ connection.sendMessage(message.toString());
+ }
+ } catch (IOException e) {
+ log.warn("Unable to send message {} to GUI due to {}", message, e);
+ log.debug("Boom!!!", e);
+ }
+ }
+
+ // Creates new message handlers.
+ private void createHandlers() {
+ handlers = new HashMap<>();
+ UiExtensionService service = directory.get(UiExtensionService.class);
+ service.getExtensions().forEach(ext -> ext.messageHandlerFactory().newHandlers().forEach(handler -> {
+ handler.init(this, directory);
+ handler.messageTypes().forEach(type -> handlers.put(type, handler));
+ }));
+ }
+
+ // Destroys message handlers.
+ private synchronized void destroyHandlers() {
+ handlers.forEach((type, handler) -> handler.destroy());
+ handlers.clear();
+ }
+}
+
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocketServlet.java b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocketServlet.java
new file mode 100644
index 0000000..f262202
--- /dev/null
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/UiWebSocketServlet.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2015 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.eclipse.jetty.websocket.WebSocket;
+import org.eclipse.jetty.websocket.WebSocketServlet;
+import org.onlab.osgi.DefaultServiceDirectory;
+import org.onlab.osgi.ServiceDirectory;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Web socket servlet capable of creating web sockets for the user interface.
+ */
+public class UiWebSocketServlet extends WebSocketServlet {
+
+ private static final long PING_DELAY_MS = 5000;
+
+ private ServiceDirectory directory = new DefaultServiceDirectory();
+
+ private final Set<UiWebSocket> sockets = new HashSet<>();
+ private final Timer timer = new Timer();
+ private final TimerTask pruner = new Pruner();
+
+ @Override
+ public void init() throws ServletException {
+ super.init();
+ timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
+ }
+
+ @Override
+ public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
+ UiWebSocket socket = new UiWebSocket(directory);
+ synchronized (sockets) {
+ sockets.add(socket);
+ }
+ return socket;
+ }
+
+ // Task for pruning web-sockets that are idle.
+ private class Pruner extends TimerTask {
+ @Override
+ public void run() {
+ synchronized (sockets) {
+ Iterator<UiWebSocket> it = sockets.iterator();
+ while (it.hasNext()) {
+ UiWebSocket socket = it.next();
+ if (socket.isIdle()) {
+ it.remove();
+ socket.close();
+ }
+ }
+ }
+ }
+ }
+}