blob: 040e98a4cf765a8201065486b78ddc67ac3fceaa [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
40 private static final long PING_DELAY_MS = 5000;
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -070041 private static final long IDLE_TIMEOUT_MS = 10000;
Thomas Vachuska3553b302015-03-07 14:49:43 -080042
Thomas Vachuska51f540f2015-05-27 17:26:57 -070043 private static UiWebSocketServlet instance;
Ray Milkey06297ed2018-01-22 17:13:41 -080044 private static final Object INSTANCE_LOCK = new Object();
Thomas Vachuska51f540f2015-05-27 17:26:57 -070045
Ray Milkey2014cdb2018-05-16 09:12:24 -070046 private static ServiceDirectory directory = new DefaultServiceDirectory();
Thomas Vachuska3553b302015-03-07 14:49:43 -080047
Charles Chan43455b82018-04-23 10:52:48 -070048 private final Set<UiWebSocket> sockets = Sets.newConcurrentHashSet();
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -070049
Thomas Vachuska3553b302015-03-07 14:49:43 -080050 private final Timer timer = new Timer();
51 private final TimerTask pruner = new Pruner();
Ray Milkey2014cdb2018-05-16 09:12:24 -070052 private static boolean isStopped = false;
Thomas Vachuska3553b302015-03-07 14:49:43 -080053
Ray Milkeyd84f89b2018-08-17 14:54:17 -070054 @Override
55 public void configure(WebSocketServletFactory webSocketServletFactory) {
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -070056 webSocketServletFactory.getPolicy().setIdleTimeout(IDLE_TIMEOUT_MS);
57 webSocketServletFactory.setCreator(new UiWebSocketCreator());
Ray Milkeyd84f89b2018-08-17 14:54:17 -070058 }
59
Thomas Vachuska51f540f2015-05-27 17:26:57 -070060 /**
61 * Closes all currently open UI web-sockets.
62 */
63 public static void closeAll() {
Ray Milkey06297ed2018-01-22 17:13:41 -080064 synchronized (INSTANCE_LOCK) {
65 if (instance != null) {
Ray Milkey2014cdb2018-05-16 09:12:24 -070066 isStopped = true;
Ray Milkey06297ed2018-01-22 17:13:41 -080067 instance.sockets.forEach(UiWebSocket::close);
68 instance.sockets.clear();
69 instance.pruner.cancel();
70 instance.timer.cancel();
71 }
Thomas Vachuska51f540f2015-05-27 17:26:57 -070072 }
73 }
74
Thomas Vachuska3553b302015-03-07 14:49:43 -080075 @Override
76 public void init() throws ServletException {
77 super.init();
Ray Milkey06297ed2018-01-22 17:13:41 -080078 synchronized (INSTANCE_LOCK) {
79 instance = this;
80 }
Thomas Vachuska3553b302015-03-07 14:49:43 -080081 timer.schedule(pruner, PING_DELAY_MS, PING_DELAY_MS);
82 }
83
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070084 /**
85 * Sends the specified message to all the GUI clients.
86 *
87 * @param type message type
88 * @param payload message payload
89 */
90 static void sendToAll(String type, ObjectNode payload) {
Ray Milkey06297ed2018-01-22 17:13:41 -080091 synchronized (INSTANCE_LOCK) {
92 if (instance != null) {
93 instance.sockets.forEach(ws -> ws.sendMessage(type, payload));
94 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070095 }
96 }
97
Thomas Vachuska0af26912016-03-21 21:37:30 -070098 /**
99 * Sends the specified message to all the GUI clients of the specified user.
100 *
101 * @param userName user name
102 * @param type message type
103 * @param payload message payload
104 */
105 static void sendToUser(String userName, String type, ObjectNode payload) {
Ray Milkey06297ed2018-01-22 17:13:41 -0800106 synchronized (INSTANCE_LOCK) {
107 if (instance != null) {
108 instance.sockets.stream().filter(ws -> userName.equals(ws.userName()))
109 .forEach(ws -> ws.sendMessage(type, payload));
110 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700111 }
112 }
113
Thomas Vachuska3553b302015-03-07 14:49:43 -0800114 // Task for pruning web-sockets that are idle.
115 private class Pruner extends TimerTask {
116 @Override
117 public void run() {
Thomas Vachuskaf3897232018-04-25 12:50:32 -0400118 ImmutableSet<UiWebSocket> set = ImmutableSet.copyOf(sockets);
119 set.stream().filter(UiWebSocket::isIdle).forEach(s -> {
120 sockets.remove(s);
121 s.close();
122 });
Thomas Vachuska3553b302015-03-07 14:49:43 -0800123 }
124 }
Thomas Vachuskad0d7ea12018-10-10 10:27:04 -0700125
126 // FIXME: This should not be necessary
127 private static final String FAKE_USERNAME = "UI-user";
128
129 public class UiWebSocketCreator implements WebSocketCreator {
130 @Override
131 public Object createWebSocket(ServletUpgradeRequest request, ServletUpgradeResponse response) {
132 if (!isStopped) {
133 // FIXME: Replace this with globally shared opaque token to allow secure failover
134 Principal p = request.getUserPrincipal();
135 String userName = p != null ? p.getName() : FAKE_USERNAME;
136
137 UiWebSocket socket = new UiWebSocket(directory, userName);
138 sockets.add(socket);
139 }
140 return null;
141 }
142 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800143}