blob: e3846775ac40b405658e8119d5981014f3846a7d [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",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700123
Simon Huntcffb2862017-06-23 12:02:43 -0700124 // TODO: More to come...
Simon Hunt7379a3d2017-06-20 16:50:39 -0700125 };
126
127
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800128 private final Logger log = LoggerFactory.getLogger(getClass());
129
Simon Huntd6d3ad32017-06-21 15:27:06 -0700130 // First thing to do is to set the locale (before creating core extension).
131 private final Locale runtimeLocale = LionUtils.setupRuntimeLocale();
132
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800133 // List of all extensions
134 private final List<UiExtension> extensions = Lists.newArrayList();
135
136 // Map of views to extensions
137 private final Map<String, UiExtension> views = Maps.newHashMap();
138
139 // Core views & core extension
Thomas Vachuska3553b302015-03-07 14:49:43 -0800140 private final UiExtension core = createCoreExtension();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800141
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700142 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
143 protected MastershipService mastershipService;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800144
Thomas Vachuska0af26912016-03-21 21:37:30 -0700145 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
146 protected StorageService storageService;
147
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800148 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
149 private UiSharedTopologyModel sharedModel;
150
Thomas Vachuska0af26912016-03-21 21:37:30 -0700151 // User preferences
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700152 private ConsistentMap<String, ObjectNode> prefsConsistentMap;
153 private Map<String, ObjectNode> prefs;
154 private final MapEventListener<String, ObjectNode> prefsListener =
Thomas Vachuska0af26912016-03-21 21:37:30 -0700155 new InternalPrefsListener();
156
Simon Hunt1169c952017-06-05 11:20:11 -0700157 // Session tokens
158 private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
159 private Map<UiSessionToken, String> tokens;
160 private final SessionTokenGenerator tokenGen =
161 new SessionTokenGenerator();
162
Thomas Vachuska0af26912016-03-21 21:37:30 -0700163 private final ObjectMapper mapper = new ObjectMapper();
164
Madan Jampanibf8ee802016-05-04 14:07:36 -0700165 private final ExecutorService eventHandlingExecutor =
Simon Huntd5b96732016-07-08 13:22:27 -0700166 Executors.newSingleThreadExecutor(
167 Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Madan Jampanibf8ee802016-05-04 14:07:36 -0700168
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700169 private LionBundle navLion;
170
171
172 private String lionNavText(String id) {
173 return navLion.getValue("nav_item_" + id);
174 }
175
176 private UiView mkView(UiView.Category cat, String id, String iconId) {
177 return new UiView(cat, id, lionNavText(id), iconId);
178 }
179
Simon Huntc54cd1b2015-05-11 13:43:44 -0700180 private UiExtension createCoreExtension() {
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700181 List<LionBundle> lionBundles = generateBundles(LION_BASE, LION_TAGS);
182
183 navLion = lionBundles.stream()
184 .filter(f -> f.id().equals("core.fw.Nav")).findFirst().get();
185
Simon Hunt20e16792015-04-24 14:29:39 -0700186 List<UiView> coreViews = of(
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700187 mkView(PLATFORM, "app", "nav_apps"),
188 mkView(PLATFORM, "settings", "nav_settings"),
189 mkView(PLATFORM, "cluster", "nav_cluster"),
190 mkView(PLATFORM, "processor", "nav_processors"),
191 mkView(PLATFORM, "partition", "nav_partitions"),
192
193 mkView(NETWORK, "topo", "nav_topo"),
194 mkView(NETWORK, "topo2", "nav_topo2"),
195 mkView(NETWORK, "device", "nav_devs"),
196
Simon Hunt20e16792015-04-24 14:29:39 -0700197 new UiViewHidden("flow"),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700198 new UiViewHidden("port"),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700199 new UiViewHidden("group"),
Jian Li1f544732015-12-30 23:36:37 -0800200 new UiViewHidden("meter"),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700201 new UiViewHidden("pipeconf"),
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700202
203 mkView(NETWORK, "link", "nav_links"),
204 mkView(NETWORK, "host", "nav_hosts"),
205 mkView(NETWORK, "intent", "nav_intents"),
206 mkView(NETWORK, "tunnel", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700207 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700208
Simon Hunt4c7edd32015-03-11 10:42:53 -0700209 UiMessageHandlerFactory messageHandlerFactory =
210 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700211 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700212 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700213 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700214 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700215 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700216 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700217 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700218 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700219 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700220 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700221 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800222 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700223 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700224 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700225 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500226 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700227 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800228 new TunnelViewMessageHandler(),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700229 new PartitionViewMessageHandler(),
230 new PipeconfViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700231 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700232
Simon Hunte05cae42015-07-23 17:35:24 -0700233 UiTopoOverlayFactory topoOverlayFactory =
234 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800235 new TrafficOverlay(),
236 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700237 );
238
Simon Hunt6cc86452017-04-27 17:46:22 -0700239 UiTopo2OverlayFactory topo2OverlayFactory =
240 () -> ImmutableList.of(
241 new Traffic2Overlay()
242 );
243
Steven Burrows3a9a6442016-05-05 15:31:16 +0100244 UiTopoMapFactory topoMapFactory =
245 () -> ImmutableList.of(
246 new UiTopoMap("australia", "Australia", "*australia", 1.0),
247 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
248 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
249 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700250 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100251 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700252 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100253 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700254 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100255 new UiTopoMap("japan", "Japan", "*japan", 0.8),
256 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
257 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
258 new UiTopoMap("africa", "Africa", "*africa", 0.7),
259 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
260 new UiTopoMap("asia", "Asia", "*asia", 0.7)
261 );
262
Simon Hunte05cae42015-07-23 17:35:24 -0700263 return new UiExtension.Builder(CL, coreViews)
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700264 .lionBundles(lionBundles)
Simon Hunte05cae42015-07-23 17:35:24 -0700265 .messageHandlerFactory(messageHandlerFactory)
266 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700267 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100268 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700269 .resourcePath(CORE)
270 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800271 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800272
Simon Hunt7379a3d2017-06-20 16:50:39 -0700273
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800274 @Activate
275 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700276 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700277 ObjectNode.class, ArrayNode.class,
278 JsonNodeFactory.class, LinkedHashMap.class,
279 TextNode.class, BooleanNode.class,
280 LongNode.class, DoubleNode.class, ShortNode.class,
281 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700282
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700283 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700284 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700285 .withSerializer(serializer)
286 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700287 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700288 prefsConsistentMap.addListener(prefsListener);
289 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700290
291 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
292 .withName(ONOS_SESSION_TOKENS)
293 .withSerializer(serializer)
294 .withRelaxedReadConsistency()
295 .build();
296 tokens = tokensConsistentMap.asJavaMap();
297
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800298 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700299
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800300 log.info("Started");
301 }
302
303 @Deactivate
304 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700305 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700306 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700307 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800308 unregister(core);
309 log.info("Stopped");
310 }
311
312 @Override
313 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900314 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800315 if (!extensions.contains(extension)) {
316 extensions.add(extension);
317 for (UiView view : extension.views()) {
318 views.put(view.id(), extension);
319 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700320 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800321 }
322 }
323
324 @Override
325 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900326 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800327 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700328 extension.views().stream()
329 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700330 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800331 }
332
333 @Override
334 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900335 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800336 return ImmutableList.copyOf(extensions);
337 }
338
339 @Override
340 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900341 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800342 return views.get(viewId);
343 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700344
Thomas Vachuska0af26912016-03-21 21:37:30 -0700345 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700346 public synchronized LionBundle getNavLionBundle() {
347 return navLion;
348 }
349
350 @Override
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800351 public void refreshModel() {
352 sharedModel.reload();
353 }
354
355 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700356 public Set<String> getUserNames() {
357 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
358 prefs.keySet().forEach(k -> builder.add(userName(k)));
359 return builder.build();
360 }
361
362 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700363 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700364 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
365 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700366 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700367 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
368 return builder.build();
369 }
370
371 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700372 public ObjectNode getPreference(String username, String key) {
373 return prefs.get(key(username, key));
374 }
375
376 @Override
377 public void setPreference(String username, String key, ObjectNode value) {
Thomas Vachuska99b7bbe2018-02-01 15:29:46 -0800378 if (value != null) {
379 prefs.put(key(username, key), value);
380 } else {
381 prefs.remove(key(username, key));
382 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700383 }
384
Simon Huntc54cd1b2015-05-11 13:43:44 -0700385 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700386 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700387
388 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700389
390 @Override
391 public Set<String> getNames() {
392 return ImmutableSet.copyOf(sprites.keySet());
393 }
394
395 @Override
396 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700397 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700398 sprites.put(name, spriteData);
399 }
400
401 @Override
402 public JsonNode get(String name) {
403 return sprites.get(name);
404 }
405
Thomas Vachuska0af26912016-03-21 21:37:30 -0700406 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700407 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700408 }
409
Simon Hunt3678c2a2016-03-28 14:48:07 -0700410
Thomas Vachuska0af26912016-03-21 21:37:30 -0700411 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700412 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700413 }
414
415 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700416 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700417 }
418
Simon Hunt1169c952017-06-05 11:20:11 -0700419
420 // =====================================================================
421 // UiTokenService
422
423 @Override
424 public UiSessionToken issueToken(String username) {
425 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
426 tokens.put(token, username);
427 log.debug("UiSessionToken issued: {}", token);
428 return token;
429 }
430
431 @Override
432 public void revokeToken(UiSessionToken token) {
433 if (token != null) {
434 tokens.remove(token);
435 log.debug("UiSessionToken revoked: {}", token);
436 }
437 }
438
439 @Override
440 public boolean isTokenValid(UiSessionToken token) {
441 return token != null && tokens.containsKey(token);
442 }
443
444 private final class SessionTokenGenerator {
445 private final SecureRandom random = new SecureRandom();
446
447 /*
448 This works by choosing 130 bits from a cryptographically secure
449 random bit generator, and encoding them in base-32.
450
451 128 bits is considered to be cryptographically strong, but each
452 digit in a base 32 number can encode 5 bits, so 128 is rounded up
453 to the next multiple of 5.
454
455 This encoding is compact and efficient, with 5 random bits per
456 character. Compare this to a random UUID, which only has 3.4 bits
457 per character in standard layout, and only 122 random bits in total.
458
459 Note that SecureRandom objects are expensive to initialize, so
460 we'll want to keep it around and re-use it.
461 */
462
463 private String nextSessionId() {
464 return new BigInteger(130, random).toString(32);
465 }
466 }
467
Thomas Vachuska0af26912016-03-21 21:37:30 -0700468 // Auxiliary listener to preference map events.
469 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700470 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700471 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700472 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700473 eventHandlingExecutor.execute(() -> {
474 String userName = userName(event.key());
475 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
476 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
477 }
478 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700479 }
480
481 private ObjectNode jsonPrefs() {
482 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700483 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700484 return json;
485 }
486 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800487}