blob: 9b7472be595beb558fe320f888d1638f5ecd3476 [file] [log] [blame]
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -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 Vachuska9ed335b2015-04-14 12:07:47 -070018import com.fasterxml.jackson.databind.JsonNode;
Thomas Vachuska0af26912016-03-21 21:37:30 -070019import com.fasterxml.jackson.databind.ObjectMapper;
20import com.fasterxml.jackson.databind.node.ArrayNode;
21import com.fasterxml.jackson.databind.node.BooleanNode;
22import com.fasterxml.jackson.databind.node.DoubleNode;
23import com.fasterxml.jackson.databind.node.IntNode;
24import com.fasterxml.jackson.databind.node.JsonNodeFactory;
25import com.fasterxml.jackson.databind.node.LongNode;
26import com.fasterxml.jackson.databind.node.NullNode;
27import com.fasterxml.jackson.databind.node.ObjectNode;
28import com.fasterxml.jackson.databind.node.ShortNode;
29import com.fasterxml.jackson.databind.node.TextNode;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080030import com.google.common.collect.ImmutableList;
Thomas Vachuska0af26912016-03-21 21:37:30 -070031import com.google.common.collect.ImmutableMap;
Thomas Vachuska9ed335b2015-04-14 12:07:47 -070032import com.google.common.collect.ImmutableSet;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080033import com.google.common.collect.Lists;
34import com.google.common.collect.Maps;
35import org.apache.felix.scr.annotations.Activate;
36import org.apache.felix.scr.annotations.Component;
37import org.apache.felix.scr.annotations.Deactivate;
Thomas Vachuska51f540f2015-05-27 17:26:57 -070038import org.apache.felix.scr.annotations.Reference;
39import org.apache.felix.scr.annotations.ReferenceCardinality;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080040import org.apache.felix.scr.annotations.Service;
Madan Jampanibf8ee802016-05-04 14:07:36 -070041import org.onlab.util.Tools;
Thomas Vachuska51f540f2015-05-27 17:26:57 -070042import org.onosproject.mastership.MastershipService;
Thomas Vachuska0af26912016-03-21 21:37:30 -070043import org.onosproject.store.serializers.KryoNamespaces;
Madan Jampani7b93ceb2016-05-04 09:58:40 -070044import org.onosproject.store.service.ConsistentMap;
45import org.onosproject.store.service.MapEvent;
46import org.onosproject.store.service.MapEventListener;
47import org.onosproject.store.service.Serializer;
Thomas Vachuska0af26912016-03-21 21:37:30 -070048import org.onosproject.store.service.StorageService;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080049import org.onosproject.ui.UiExtension;
50import org.onosproject.ui.UiExtensionService;
Thomas Vachuska3553b302015-03-07 14:49:43 -080051import org.onosproject.ui.UiMessageHandlerFactory;
Thomas Vachuska0af26912016-03-21 21:37:30 -070052import org.onosproject.ui.UiPreferencesService;
Simon Hunt1169c952017-06-05 11:20:11 -070053import org.onosproject.ui.UiSessionToken;
54import org.onosproject.ui.UiTokenService;
Simon Hunt6cc86452017-04-27 17:46:22 -070055import org.onosproject.ui.UiTopo2OverlayFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070056import org.onosproject.ui.UiTopoMap;
Steven Burrows3a9a6442016-05-05 15:31:16 +010057import org.onosproject.ui.UiTopoMapFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070058import org.onosproject.ui.UiTopoOverlayFactory;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080059import org.onosproject.ui.UiView;
Simon Hunt1002cd82015-04-23 14:44:03 -070060import org.onosproject.ui.UiViewHidden;
Simon Hunte6f64612017-04-28 00:01:48 -070061import org.onosproject.ui.impl.topo.Topo2TrafficMessageHandler;
Simon Huntd5b96732016-07-08 13:22:27 -070062import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
Simon Hunt6cc86452017-04-27 17:46:22 -070063import org.onosproject.ui.impl.topo.Traffic2Overlay;
Thomas Vachuska1b1355d2018-02-06 16:53:58 -080064import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
Simon Hunt23f9c7b2017-07-10 20:00:30 -070065import org.onosproject.ui.lion.LionBundle;
Simon Huntb8042032017-06-13 15:03:23 -070066import org.onosproject.ui.lion.LionUtils;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080067import org.slf4j.Logger;
68import org.slf4j.LoggerFactory;
69
Simon Hunt1169c952017-06-05 11:20:11 -070070import java.math.BigInteger;
71import java.security.SecureRandom;
Thomas Vachuska0af26912016-03-21 21:37:30 -070072import java.util.LinkedHashMap;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080073import java.util.List;
Simon Huntd6d3ad32017-06-21 15:27:06 -070074import java.util.Locale;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080075import java.util.Map;
Thomas Vachuska9ed335b2015-04-14 12:07:47 -070076import java.util.Set;
Madan Jampanibf8ee802016-05-04 14:07:36 -070077import java.util.concurrent.ExecutorService;
78import java.util.concurrent.Executors;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080079
80import static com.google.common.collect.ImmutableList.of;
81import static java.util.stream.Collectors.toSet;
Heedo Kang4a47a302016-02-29 17:40:23 +090082import static org.onosproject.security.AppGuard.checkPermission;
83import static org.onosproject.security.AppPermission.Type.UI_READ;
84import static org.onosproject.security.AppPermission.Type.UI_WRITE;
Thomas Vachuska0af26912016-03-21 21:37:30 -070085import static org.onosproject.ui.UiView.Category.NETWORK;
86import static org.onosproject.ui.UiView.Category.PLATFORM;
Simon Huntd8754652017-06-21 11:45:22 -070087import static org.onosproject.ui.impl.lion.BundleStitcher.generateBundles;
Heedo Kang4a47a302016-02-29 17:40:23 +090088
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080089/**
90 * Manages the user interface extensions.
91 */
92@Component(immediate = true)
93@Service
Simon Hunt3678c2a2016-03-28 14:48:07 -070094public class UiExtensionManager
Simon Hunt1169c952017-06-05 11:20:11 -070095 implements UiExtensionService, UiPreferencesService, SpriteService,
96 UiTokenService {
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080097
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070098 private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
Simon Hunt3678c2a2016-03-28 14:48:07 -070099
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700100 private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
Simon Hunt1169c952017-06-05 11:20:11 -0700101 private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
Simon Hunte05cae42015-07-23 17:35:24 -0700102 private static final String CORE = "core";
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700103 private static final String GUI_ADDED = "guiAdded";
104 private static final String GUI_REMOVED = "guiRemoved";
Thomas Vachuska0af26912016-03-21 21:37:30 -0700105 private static final String UPDATE_PREFS = "updatePrefs";
Simon Hunt3678c2a2016-03-28 14:48:07 -0700106 private static final String SLASH = "/";
107
108 private static final int IDX_USER = 0;
109 private static final int IDX_KEY = 1;
Simon Hunte05cae42015-07-23 17:35:24 -0700110
Simon Hunt7379a3d2017-06-20 16:50:39 -0700111 private static final String LION_BASE = "/org/onosproject/ui/lion";
112
113 private static final String[] LION_TAGS = {
Simon Huntcffb2862017-06-23 12:02:43 -0700114 // framework component localization
115 "core.fw.Mast",
116 "core.fw.Nav",
117 "core.fw.QuickHelp",
118
119 // view component localization
120 "core.view.App",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700121 "core.view.Cluster",
Simon Hunt5989ddf2017-08-02 20:38:12 -0700122 "core.view.Topo",
ArturoF Velasco604ad0b2018-01-03 00:43:58 +0100123 "core.view.Flow",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700124
Simon Huntcffb2862017-06-23 12:02:43 -0700125 // TODO: More to come...
Simon Hunt7379a3d2017-06-20 16:50:39 -0700126 };
127
128
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800129 private final Logger log = LoggerFactory.getLogger(getClass());
130
Simon Huntd6d3ad32017-06-21 15:27:06 -0700131 // First thing to do is to set the locale (before creating core extension).
132 private final Locale runtimeLocale = LionUtils.setupRuntimeLocale();
133
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800134 // List of all extensions
135 private final List<UiExtension> extensions = Lists.newArrayList();
136
137 // Map of views to extensions
138 private final Map<String, UiExtension> views = Maps.newHashMap();
139
140 // Core views & core extension
Thomas Vachuska3553b302015-03-07 14:49:43 -0800141 private final UiExtension core = createCoreExtension();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800142
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700143 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
144 protected MastershipService mastershipService;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800145
Thomas Vachuska0af26912016-03-21 21:37:30 -0700146 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
147 protected StorageService storageService;
148
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800149 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
150 private UiSharedTopologyModel sharedModel;
151
Thomas Vachuska0af26912016-03-21 21:37:30 -0700152 // User preferences
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700153 private ConsistentMap<String, ObjectNode> prefsConsistentMap;
154 private Map<String, ObjectNode> prefs;
155 private final MapEventListener<String, ObjectNode> prefsListener =
Thomas Vachuska0af26912016-03-21 21:37:30 -0700156 new InternalPrefsListener();
157
Simon Hunt1169c952017-06-05 11:20:11 -0700158 // Session tokens
159 private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
160 private Map<UiSessionToken, String> tokens;
161 private final SessionTokenGenerator tokenGen =
162 new SessionTokenGenerator();
163
Thomas Vachuska0af26912016-03-21 21:37:30 -0700164 private final ObjectMapper mapper = new ObjectMapper();
165
Madan Jampanibf8ee802016-05-04 14:07:36 -0700166 private final ExecutorService eventHandlingExecutor =
Simon Huntd5b96732016-07-08 13:22:27 -0700167 Executors.newSingleThreadExecutor(
168 Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Madan Jampanibf8ee802016-05-04 14:07:36 -0700169
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700170 private LionBundle navLion;
171
172
173 private String lionNavText(String id) {
174 return navLion.getValue("nav_item_" + id);
175 }
176
177 private UiView mkView(UiView.Category cat, String id, String iconId) {
178 return new UiView(cat, id, lionNavText(id), iconId);
179 }
180
Simon Huntc54cd1b2015-05-11 13:43:44 -0700181 private UiExtension createCoreExtension() {
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700182 List<LionBundle> lionBundles = generateBundles(LION_BASE, LION_TAGS);
183
184 navLion = lionBundles.stream()
185 .filter(f -> f.id().equals("core.fw.Nav")).findFirst().get();
186
Simon Hunt20e16792015-04-24 14:29:39 -0700187 List<UiView> coreViews = of(
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700188 mkView(PLATFORM, "app", "nav_apps"),
189 mkView(PLATFORM, "settings", "nav_settings"),
190 mkView(PLATFORM, "cluster", "nav_cluster"),
191 mkView(PLATFORM, "processor", "nav_processors"),
192 mkView(PLATFORM, "partition", "nav_partitions"),
193
194 mkView(NETWORK, "topo", "nav_topo"),
195 mkView(NETWORK, "topo2", "nav_topo2"),
196 mkView(NETWORK, "device", "nav_devs"),
197
Simon Hunt20e16792015-04-24 14:29:39 -0700198 new UiViewHidden("flow"),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700199 new UiViewHidden("port"),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700200 new UiViewHidden("group"),
Jian Li1f544732015-12-30 23:36:37 -0800201 new UiViewHidden("meter"),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700202 new UiViewHidden("pipeconf"),
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700203
204 mkView(NETWORK, "link", "nav_links"),
205 mkView(NETWORK, "host", "nav_hosts"),
206 mkView(NETWORK, "intent", "nav_intents"),
207 mkView(NETWORK, "tunnel", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700208 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700209
Simon Hunt4c7edd32015-03-11 10:42:53 -0700210 UiMessageHandlerFactory messageHandlerFactory =
211 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700212 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700213 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700214 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700215 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700216 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700217 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700218 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700219 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700220 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700221 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700222 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800223 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700224 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700225 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700226 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500227 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700228 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800229 new TunnelViewMessageHandler(),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700230 new PartitionViewMessageHandler(),
231 new PipeconfViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700232 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700233
Simon Hunte05cae42015-07-23 17:35:24 -0700234 UiTopoOverlayFactory topoOverlayFactory =
235 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800236 new TrafficOverlay(),
237 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700238 );
239
Simon Hunt6cc86452017-04-27 17:46:22 -0700240 UiTopo2OverlayFactory topo2OverlayFactory =
241 () -> ImmutableList.of(
242 new Traffic2Overlay()
243 );
244
Steven Burrows3a9a6442016-05-05 15:31:16 +0100245 UiTopoMapFactory topoMapFactory =
246 () -> ImmutableList.of(
247 new UiTopoMap("australia", "Australia", "*australia", 1.0),
248 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
249 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
250 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700251 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100252 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700253 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100254 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700255 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100256 new UiTopoMap("japan", "Japan", "*japan", 0.8),
257 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
258 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
259 new UiTopoMap("africa", "Africa", "*africa", 0.7),
260 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
261 new UiTopoMap("asia", "Asia", "*asia", 0.7)
262 );
263
Simon Hunte05cae42015-07-23 17:35:24 -0700264 return new UiExtension.Builder(CL, coreViews)
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700265 .lionBundles(lionBundles)
Simon Hunte05cae42015-07-23 17:35:24 -0700266 .messageHandlerFactory(messageHandlerFactory)
267 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700268 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100269 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700270 .resourcePath(CORE)
271 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800272 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800273
Simon Hunt7379a3d2017-06-20 16:50:39 -0700274
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800275 @Activate
276 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700277 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700278 ObjectNode.class, ArrayNode.class,
279 JsonNodeFactory.class, LinkedHashMap.class,
280 TextNode.class, BooleanNode.class,
281 LongNode.class, DoubleNode.class, ShortNode.class,
282 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700283
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700284 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700285 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700286 .withSerializer(serializer)
287 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700288 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700289 prefsConsistentMap.addListener(prefsListener);
290 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700291
292 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
293 .withName(ONOS_SESSION_TOKENS)
294 .withSerializer(serializer)
295 .withRelaxedReadConsistency()
296 .build();
297 tokens = tokensConsistentMap.asJavaMap();
298
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800299 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700300
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800301 log.info("Started");
302 }
303
304 @Deactivate
305 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700306 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700307 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700308 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800309 unregister(core);
310 log.info("Stopped");
311 }
312
313 @Override
314 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900315 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800316 if (!extensions.contains(extension)) {
317 extensions.add(extension);
318 for (UiView view : extension.views()) {
319 views.put(view.id(), extension);
320 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700321 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800322 }
323 }
324
325 @Override
326 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900327 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800328 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700329 extension.views().stream()
330 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700331 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800332 }
333
334 @Override
335 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900336 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800337 return ImmutableList.copyOf(extensions);
338 }
339
340 @Override
341 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900342 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800343 return views.get(viewId);
344 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700345
Thomas Vachuska0af26912016-03-21 21:37:30 -0700346 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700347 public synchronized LionBundle getNavLionBundle() {
348 return navLion;
349 }
350
351 @Override
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800352 public void refreshModel() {
353 sharedModel.reload();
354 }
355
356 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700357 public Set<String> getUserNames() {
358 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
359 prefs.keySet().forEach(k -> builder.add(userName(k)));
360 return builder.build();
361 }
362
363 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700364 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700365 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
366 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700367 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700368 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
369 return builder.build();
370 }
371
372 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700373 public ObjectNode getPreference(String username, String key) {
374 return prefs.get(key(username, key));
375 }
376
377 @Override
378 public void setPreference(String username, String key, ObjectNode value) {
Thomas Vachuska99b7bbe2018-02-01 15:29:46 -0800379 if (value != null) {
380 prefs.put(key(username, key), value);
381 } else {
382 prefs.remove(key(username, key));
383 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700384 }
385
Simon Huntc54cd1b2015-05-11 13:43:44 -0700386 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700387 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700388
389 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700390
391 @Override
392 public Set<String> getNames() {
393 return ImmutableSet.copyOf(sprites.keySet());
394 }
395
396 @Override
397 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700398 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700399 sprites.put(name, spriteData);
400 }
401
402 @Override
403 public JsonNode get(String name) {
404 return sprites.get(name);
405 }
406
Thomas Vachuska0af26912016-03-21 21:37:30 -0700407 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700408 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700409 }
410
Simon Hunt3678c2a2016-03-28 14:48:07 -0700411
Thomas Vachuska0af26912016-03-21 21:37:30 -0700412 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700413 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700414 }
415
416 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700417 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700418 }
419
Simon Hunt1169c952017-06-05 11:20:11 -0700420
421 // =====================================================================
422 // UiTokenService
423
424 @Override
425 public UiSessionToken issueToken(String username) {
426 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
427 tokens.put(token, username);
428 log.debug("UiSessionToken issued: {}", token);
429 return token;
430 }
431
432 @Override
433 public void revokeToken(UiSessionToken token) {
434 if (token != null) {
435 tokens.remove(token);
436 log.debug("UiSessionToken revoked: {}", token);
437 }
438 }
439
440 @Override
441 public boolean isTokenValid(UiSessionToken token) {
442 return token != null && tokens.containsKey(token);
443 }
444
445 private final class SessionTokenGenerator {
446 private final SecureRandom random = new SecureRandom();
447
448 /*
449 This works by choosing 130 bits from a cryptographically secure
450 random bit generator, and encoding them in base-32.
451
452 128 bits is considered to be cryptographically strong, but each
453 digit in a base 32 number can encode 5 bits, so 128 is rounded up
454 to the next multiple of 5.
455
456 This encoding is compact and efficient, with 5 random bits per
457 character. Compare this to a random UUID, which only has 3.4 bits
458 per character in standard layout, and only 122 random bits in total.
459
460 Note that SecureRandom objects are expensive to initialize, so
461 we'll want to keep it around and re-use it.
462 */
463
464 private String nextSessionId() {
465 return new BigInteger(130, random).toString(32);
466 }
467 }
468
Thomas Vachuska0af26912016-03-21 21:37:30 -0700469 // Auxiliary listener to preference map events.
470 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700471 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700472 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700473 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700474 eventHandlingExecutor.execute(() -> {
475 String userName = userName(event.key());
476 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
477 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
478 }
479 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700480 }
481
482 private ObjectNode jsonPrefs() {
483 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700484 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700485 return json;
486 }
487 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800488}