blob: e9a4cbdb3b1c8bffb0b867874965db0063f80b6b [file] [log] [blame]
Thomas Vachuska3553b302015-03-07 14:49:43 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuska3553b302015-03-07 14:49:43 -08003 *
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
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070018import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuskaf3897232018-04-25 12:50:32 -040019import com.google.common.collect.ImmutableSet;
Charles Chan43455b82018-04-23 10:52:48 -070020import com.google.common.collect.Sets;
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -070021import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
22import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
23import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070024import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
25import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
Thomas Vachuska3553b302015-03-07 14:49:43 -080026import org.onlab.osgi.DefaultServiceDirectory;
27import org.onlab.osgi.ServiceDirectory;
28
29import javax.servlet.ServletException;
Simon Huntea327082016-04-11 16:49:07 -070030import java.security.Principal;
Thomas Vachuska3553b302015-03-07 14:49:43 -080031import java.util.Set;
32import java.util.Timer;
33import java.util.TimerTask;
34
35/**
36 * Web socket servlet capable of creating web sockets for the user interface.
37 */
38public class UiWebSocketServlet extends WebSocketServlet {
39
Thomas Vachuskac0f757a2018-10-23 11:41:34 -070040 static final long PING_DELAY_MS = 5000;
Thomas Vachuska3553b302015-03-07 14:49:43 -080041
Thomas Vachuska51f540f2015-05-27 17:26:57 -070042 private static UiWebSocketServlet instance;
Ray Milkey06297ed2018-01-22 17:13:41 -080043 private static final Object INSTANCE_LOCK = new Object();
Thomas Vachuska51f540f2015-05-27 17:26:57 -070044
Ray Milkey2014cdb2018-05-16 09:12:24 -070045 private static ServiceDirectory directory = new DefaultServiceDirectory();
Thomas Vachuska3553b302015-03-07 14:49:43 -080046
Charles Chan43455b82018-04-23 10:52:48 -070047 private final Set<UiWebSocket> sockets = Sets.newConcurrentHashSet();
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -070048
Thomas Vachuska3553b302015-03-07 14:49:43 -080049 private final Timer timer = new Timer();
50 private final TimerTask pruner = new Pruner();
Ray Milkey2014cdb2018-05-16 09:12:24 -070051 private static boolean isStopped = false;
Thomas Vachuska3553b302015-03-07 14:49:43 -080052
Ray Milkeyd84f89b2018-08-17 14:54:17 -070053 @Override
54 public void configure(WebSocketServletFactory webSocketServletFactory) {
Thomas Vachuskac0f757a2018-10-23 11:41:34 -070055 webSocketServletFactory.getPolicy().setIdleTimeout(Long.MAX_VALUE);
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -070056 webSocketServletFactory.setCreator(new UiWebSocketCreator());
Ray Milkeyd84f89b2018-08-17 14:54:17 -070057 }
58
Thomas Vachuska51f540f2015-05-27 17:26:57 -070059 /**
60 * Closes all currently open UI web-sockets.
61 */
62 public static void closeAll() {
Ray Milkey06297ed2018-01-22 17:13:41 -080063 synchronized (INSTANCE_LOCK) {
64 if (instance != null) {
Ray Milkey2014cdb2018-05-16 09:12:24 -070065 isStopped = true;
Ray Milkey06297ed2018-01-22 17:13:41 -080066 instance.sockets.forEach(UiWebSocket::close);
67 instance.sockets.clear();
68 instance.pruner.cancel();
69 instance.timer.cancel();
70 }
Thomas Vachuska51f540f2015-05-27 17:26:57 -070071 }
72 }
73
Thomas Vachuska3553b302015-03-07 14:49:43 -080074 @Override
75 public void init() throws ServletException {
76 super.init();
Ray Milkey06297ed2018-01-22 17:13:41 -080077 synchronized (INSTANCE_LOCK) {
78 instance = this;
79 }
Thomas Vachuska3553b302015-03-07 14:49:43 -080080 timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
81 }
82
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070083 /**
84 * Sends the specified message to all the GUI clients.
85 *
86 * @param type message type
87 * @param payload message payload
88 */
89 static void sendToAll(String type, ObjectNode payload) {
Ray Milkey06297ed2018-01-22 17:13:41 -080090 synchronized (INSTANCE_LOCK) {
91 if (instance != null) {
92 instance.sockets.forEach(ws -> ws.sendMessage(type, payload));
93 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070094 }
95 }
96
Thomas Vachuska0af26912016-03-21 21:37:30 -070097 /**
98 * Sends the specified message to all the GUI clients of the specified user.
99 *
100 * @param userName user name
101 * @param type message type
102 * @param payload message payload
103 */
104 static void sendToUser(String userName, String type, ObjectNode payload) {
Ray Milkey06297ed2018-01-22 17:13:41 -0800105 synchronized (INSTANCE_LOCK) {
106 if (instance != null) {
107 instance.sockets.stream().filter(ws -> userName.equals(ws.userName()))
108 .forEach(ws -> ws.sendMessage(type, payload));
109 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700110 }
111 }
112
Thomas Vachuska3553b302015-03-07 14:49:43 -0800113 // Task for pruning web-sockets that are idle.
114 private class Pruner extends TimerTask {
115 @Override
116 public void run() {
Thomas Vachuskaf3897232018-04-25 12:50:32 -0400117 ImmutableSet<UiWebSocket> set = ImmutableSet.copyOf(sockets);
118 set.stream().filter(UiWebSocket::isIdle).forEach(s -> {
119 sockets.remove(s);
120 s.close();
121 });
Thomas Vachuska3553b302015-03-07 14:49:43 -0800122 }
123 }
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -0700124
125 // FIXME: This should not be necessary
Thomas Vachuska2bd1fc82018-10-22 13:09:14 -0700126 private static final String FAKE_USERNAME = "unknown";
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -0700127
128 public class UiWebSocketCreator implements WebSocketCreator {
129 @Override
130 public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response) {
131 if (!isStopped) {
132 // FIXME: Replace this with globally shared opaque token to allow secure failover
133 Principal p = request.getUserPrincipal();
134 String userName = p != null ? p.getName() : FAKE_USERNAME;
135
136 UiWebSocket socket = new UiWebSocket(directory, userName);
137 sockets.add(socket);
Thomas Vachuska2bd1fc82018-10-22 13:09:14 -0700138 return socket;
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -0700139 }
140 return null;
141 }
142 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800143}