blob: a35a38814fc29ffdb5d5c8ba89058d13acb599d0 [file] [log] [blame]
Thomas Vachuska3553b302015-03-07 14:49:43 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.ui.impl;
17
18import com.fasterxml.jackson.databind.ObjectMapper;
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuska3553b302015-03-07 14:49:43 -080020import com.fasterxml.jackson.databind.node.ObjectNode;
21import org.eclipse.jetty.websocket.WebSocket;
22import org.onlab.osgi.ServiceDirectory;
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070023import org.onosproject.cluster.ClusterService;
24import org.onosproject.cluster.ControllerNode;
Thomas Vachuska3553b302015-03-07 14:49:43 -080025import org.onosproject.ui.UiConnection;
26import org.onosproject.ui.UiExtensionService;
27import org.onosproject.ui.UiMessageHandler;
Thomas Vachuska329af532015-03-10 02:08:33 -070028import org.onosproject.ui.UiMessageHandlerFactory;
Thomas Vachuska3553b302015-03-07 14:49:43 -080029import org.slf4j.Logger;
30import org.slf4j.LoggerFactory;
31
32import java.io.IOException;
33import java.util.HashMap;
34import java.util.Map;
35
36/**
37 * Web socket capable of interacting with the GUI.
38 */
39public class UiWebSocket
40 implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl {
41
42 private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
43
44 private static final long MAX_AGE_MS = 15000;
45
46 private static final byte PING = 0x9;
47 private static final byte PONG = 0xA;
48 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
49
50 private final ServiceDirectory directory;
51
52 private Connection connection;
53 private FrameConnection control;
54
55 private final ObjectMapper mapper = new ObjectMapper();
56
57 private long lastActive = System.currentTimeMillis();
58
59 private Map<String, UiMessageHandler> handlers;
60
61 /**
62 * Creates a new web-socket for serving data to GUI.
63 *
64 * @param directory service directory
65 */
66 public UiWebSocket(ServiceDirectory directory) {
67 this.directory = directory;
68 }
69
70 /**
71 * Issues a close on the connection.
72 */
73 synchronized void close() {
74 destroyHandlers();
75 if (connection.isOpen()) {
76 connection.close();
77 }
78 }
79
80 /**
81 * Indicates if this connection is idle.
82 *
83 * @return true if idle or closed
84 */
85 synchronized boolean isIdle() {
86 boolean idle = (System.currentTimeMillis() - lastActive) > MAX_AGE_MS;
87 if (idle || (connection != null && !connection.isOpen())) {
88 return true;
89 } else if (connection != null) {
90 try {
91 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
92 } catch (IOException e) {
93 log.warn("Unable to send ping message due to: ", e);
94 }
95 }
96 return false;
97 }
98
99 @Override
100 public void onOpen(Connection connection) {
101 log.info("GUI client connected");
102 this.connection = connection;
103 this.control = (FrameConnection) connection;
104 createHandlers();
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700105 sendInstanceData();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800106 }
107
108 @Override
109 public synchronized void onClose(int closeCode, String message) {
110 destroyHandlers();
111 log.info("GUI client disconnected");
112 }
113
114 @Override
115 public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
116 lastActive = System.currentTimeMillis();
117 return true;
118 }
119
120 @Override
121 public void onMessage(String data) {
122 lastActive = System.currentTimeMillis();
123 try {
124 ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
Thomas Vachuska329af532015-03-10 02:08:33 -0700125 String type = message.path("event").asText("unknown");
Thomas Vachuska3553b302015-03-07 14:49:43 -0800126 UiMessageHandler handler = handlers.get(type);
127 if (handler != null) {
128 handler.process(message);
129 } else {
130 log.warn("No GUI message handler for type {}", type);
131 }
132 } catch (Exception e) {
133 log.warn("Unable to parse GUI message {} due to {}", data, e);
134 log.debug("Boom!!!", e);
135 }
136 }
137
138 @Override
139 public void sendMessage(ObjectNode message) {
140 try {
141 if (connection.isOpen()) {
142 connection.sendMessage(message.toString());
143 }
144 } catch (IOException e) {
145 log.warn("Unable to send message {} to GUI due to {}", message, e);
146 log.debug("Boom!!!", e);
147 }
148 }
149
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700150 @Override
151 public void sendMessage(String type, long sid, ObjectNode payload) {
152 ObjectNode message = mapper.createObjectNode();
153 message.put("event", type);
154 if (sid > 0) {
155 message.put("sid", sid);
156 }
157 message.set("payload", payload);
158 sendMessage(message);
159
160 }
161
Thomas Vachuska3553b302015-03-07 14:49:43 -0800162 // Creates new message handlers.
163 private void createHandlers() {
164 handlers = new HashMap<>();
165 UiExtensionService service = directory.get(UiExtensionService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700166 service.getExtensions().forEach(ext -> {
167 UiMessageHandlerFactory factory = ext.messageHandlerFactory();
168 if (factory != null) {
169 factory.newHandlers().forEach(handler -> {
170 handler.init(this, directory);
171 handler.messageTypes().forEach(type -> handlers.put(type, handler));
172 });
173 }
174 });
Thomas Vachuska3553b302015-03-07 14:49:43 -0800175 }
176
177 // Destroys message handlers.
178 private synchronized void destroyHandlers() {
179 handlers.forEach((type, handler) -> handler.destroy());
180 handlers.clear();
181 }
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700182
183 // Sends cluster node/instance information to allow GUI to fail-over.
184 private void sendInstanceData() {
185 ClusterService service = directory.get(ClusterService.class);
186 ArrayNode instances = mapper.createArrayNode();
187
188 for (ControllerNode node : service.getNodes()) {
189 ObjectNode instance = mapper.createObjectNode()
190 .put("id", node.id().toString())
191 .put("ip", node.ip().toString())
192 .put("uiAttached", node.equals(service.getLocalNode()));
193 instances.add(instance);
194 }
195
196 ObjectNode payload = mapper.createObjectNode();
Thomas Vachuska20084b72015-03-11 13:46:50 -0700197 payload.set("clusterNodes", instances);
198 sendMessage("bootstrap", 0, payload);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700199 }
200
Thomas Vachuska3553b302015-03-07 14:49:43 -0800201}
202