blob: 2c6a966a45b657970028e5b5b6b7d8070ce5c95a [file] [log] [blame]
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
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;
Simon Hunt23f9c7b2017-07-10 20:00:30 -070064import org.onosproject.ui.lion.LionBundle;
Simon Huntb8042032017-06-13 15:03:23 -070065import org.onosproject.ui.lion.LionUtils;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080066import org.slf4j.Logger;
67import org.slf4j.LoggerFactory;
68
Simon Hunt1169c952017-06-05 11:20:11 -070069import java.math.BigInteger;
70import java.security.SecureRandom;
Thomas Vachuska0af26912016-03-21 21:37:30 -070071import java.util.LinkedHashMap;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080072import java.util.List;
Simon Huntd6d3ad32017-06-21 15:27:06 -070073import java.util.Locale;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080074import java.util.Map;
Thomas Vachuska9ed335b2015-04-14 12:07:47 -070075import java.util.Set;
Madan Jampanibf8ee802016-05-04 14:07:36 -070076import java.util.concurrent.ExecutorService;
77import java.util.concurrent.Executors;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080078
79import static com.google.common.collect.ImmutableList.of;
80import static java.util.stream.Collectors.toSet;
Heedo Kang4a47a302016-02-29 17:40:23 +090081import static org.onosproject.security.AppGuard.checkPermission;
82import static org.onosproject.security.AppPermission.Type.UI_READ;
83import static org.onosproject.security.AppPermission.Type.UI_WRITE;
Thomas Vachuska0af26912016-03-21 21:37:30 -070084import static org.onosproject.ui.UiView.Category.NETWORK;
85import static org.onosproject.ui.UiView.Category.PLATFORM;
Simon Huntd8754652017-06-21 11:45:22 -070086import static org.onosproject.ui.impl.lion.BundleStitcher.generateBundles;
Heedo Kang4a47a302016-02-29 17:40:23 +090087
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080088/**
89 * Manages the user interface extensions.
90 */
91@Component(immediate = true)
92@Service
Simon Hunt3678c2a2016-03-28 14:48:07 -070093public class UiExtensionManager
Simon Hunt1169c952017-06-05 11:20:11 -070094 implements UiExtensionService, UiPreferencesService, SpriteService,
95 UiTokenService {
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080096
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070097 private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
Simon Hunt3678c2a2016-03-28 14:48:07 -070098
Madan Jampani7b93ceb2016-05-04 09:58:40 -070099 private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
Simon Hunt1169c952017-06-05 11:20:11 -0700100 private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
Simon Hunte05cae42015-07-23 17:35:24 -0700101 private static final String CORE = "core";
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700102 private static final String GUI_ADDED = "guiAdded";
103 private static final String GUI_REMOVED = "guiRemoved";
Thomas Vachuska0af26912016-03-21 21:37:30 -0700104 private static final String UPDATE_PREFS = "updatePrefs";
Simon Hunt3678c2a2016-03-28 14:48:07 -0700105 private static final String SLASH = "/";
106
107 private static final int IDX_USER = 0;
108 private static final int IDX_KEY = 1;
Simon Hunte05cae42015-07-23 17:35:24 -0700109
Simon Hunt7379a3d2017-06-20 16:50:39 -0700110 private static final String LION_BASE = "/org/onosproject/ui/lion";
111
112 private static final String[] LION_TAGS = {
Simon Huntcffb2862017-06-23 12:02:43 -0700113 // framework component localization
114 "core.fw.Mast",
115 "core.fw.Nav",
116 "core.fw.QuickHelp",
117
118 // view component localization
119 "core.view.App",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700120 "core.view.Cluster",
Simon Hunt5989ddf2017-08-02 20:38:12 -0700121 "core.view.Topo",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700122
Simon Huntcffb2862017-06-23 12:02:43 -0700123 // TODO: More to come...
Simon Hunt7379a3d2017-06-20 16:50:39 -0700124 };
125
126
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800127 private final Logger log = LoggerFactory.getLogger(getClass());
128
Simon Huntd6d3ad32017-06-21 15:27:06 -0700129 // First thing to do is to set the locale (before creating core extension).
130 private final Locale runtimeLocale = LionUtils.setupRuntimeLocale();
131
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800132 // List of all extensions
133 private final List<UiExtension> extensions = Lists.newArrayList();
134
135 // Map of views to extensions
136 private final Map<String, UiExtension> views = Maps.newHashMap();
137
138 // Core views & core extension
Thomas Vachuska3553b302015-03-07 14:49:43 -0800139 private final UiExtension core = createCoreExtension();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800140
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700141 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
142 protected MastershipService mastershipService;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800143
Thomas Vachuska0af26912016-03-21 21:37:30 -0700144 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
145 protected StorageService storageService;
146
147 // User preferences
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700148 private ConsistentMap<String, ObjectNode> prefsConsistentMap;
149 private Map<String, ObjectNode> prefs;
150 private final MapEventListener<String, ObjectNode> prefsListener =
Thomas Vachuska0af26912016-03-21 21:37:30 -0700151 new InternalPrefsListener();
152
Simon Hunt1169c952017-06-05 11:20:11 -0700153 // Session tokens
154 private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
155 private Map<UiSessionToken, String> tokens;
156 private final SessionTokenGenerator tokenGen =
157 new SessionTokenGenerator();
158
Thomas Vachuska0af26912016-03-21 21:37:30 -0700159 private final ObjectMapper mapper = new ObjectMapper();
160
Madan Jampanibf8ee802016-05-04 14:07:36 -0700161 private final ExecutorService eventHandlingExecutor =
Simon Huntd5b96732016-07-08 13:22:27 -0700162 Executors.newSingleThreadExecutor(
163 Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Madan Jampanibf8ee802016-05-04 14:07:36 -0700164
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700165 private LionBundle navLion;
166
167
168 private String lionNavText(String id) {
169 return navLion.getValue("nav_item_" + id);
170 }
171
172 private UiView mkView(UiView.Category cat, String id, String iconId) {
173 return new UiView(cat, id, lionNavText(id), iconId);
174 }
175
Simon Huntc54cd1b2015-05-11 13:43:44 -0700176 private UiExtension createCoreExtension() {
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700177 List<LionBundle> lionBundles = generateBundles(LION_BASE, LION_TAGS);
178
179 navLion = lionBundles.stream()
180 .filter(f -> f.id().equals("core.fw.Nav")).findFirst().get();
181
Simon Hunt20e16792015-04-24 14:29:39 -0700182 List<UiView> coreViews = of(
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700183 mkView(PLATFORM, "app", "nav_apps"),
184 mkView(PLATFORM, "settings", "nav_settings"),
185 mkView(PLATFORM, "cluster", "nav_cluster"),
186 mkView(PLATFORM, "processor", "nav_processors"),
187 mkView(PLATFORM, "partition", "nav_partitions"),
188
189 mkView(NETWORK, "topo", "nav_topo"),
190 mkView(NETWORK, "topo2", "nav_topo2"),
191 mkView(NETWORK, "device", "nav_devs"),
192
Simon Hunt20e16792015-04-24 14:29:39 -0700193 new UiViewHidden("flow"),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700194 new UiViewHidden("port"),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700195 new UiViewHidden("group"),
Jian Li1f544732015-12-30 23:36:37 -0800196 new UiViewHidden("meter"),
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700197
198 mkView(NETWORK, "link", "nav_links"),
199 mkView(NETWORK, "host", "nav_hosts"),
200 mkView(NETWORK, "intent", "nav_intents"),
201 mkView(NETWORK, "tunnel", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700202 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700203
Simon Hunt4c7edd32015-03-11 10:42:53 -0700204 UiMessageHandlerFactory messageHandlerFactory =
205 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700206 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700207 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700208 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700209 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700210 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700211 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700212 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700213 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700214 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700215 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700216 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800217 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700218 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700219 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700220 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500221 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700222 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800223 new TunnelViewMessageHandler(),
224 new PartitionViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700225 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700226
Simon Hunte05cae42015-07-23 17:35:24 -0700227 UiTopoOverlayFactory topoOverlayFactory =
228 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800229 new TrafficOverlay(),
230 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700231 );
232
Simon Hunt6cc86452017-04-27 17:46:22 -0700233 UiTopo2OverlayFactory topo2OverlayFactory =
234 () -> ImmutableList.of(
235 new Traffic2Overlay()
236 );
237
Steven Burrows3a9a6442016-05-05 15:31:16 +0100238 UiTopoMapFactory topoMapFactory =
239 () -> ImmutableList.of(
240 new UiTopoMap("australia", "Australia", "*australia", 1.0),
241 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
242 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
243 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700244 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100245 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700246 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100247 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700248 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100249 new UiTopoMap("japan", "Japan", "*japan", 0.8),
250 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
251 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
252 new UiTopoMap("africa", "Africa", "*africa", 0.7),
253 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
254 new UiTopoMap("asia", "Asia", "*asia", 0.7)
255 );
256
Simon Hunte05cae42015-07-23 17:35:24 -0700257 return new UiExtension.Builder(CL, coreViews)
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700258 .lionBundles(lionBundles)
Simon Hunte05cae42015-07-23 17:35:24 -0700259 .messageHandlerFactory(messageHandlerFactory)
260 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700261 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100262 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700263 .resourcePath(CORE)
264 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800265 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800266
Simon Hunt7379a3d2017-06-20 16:50:39 -0700267
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800268 @Activate
269 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700270 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700271 ObjectNode.class, ArrayNode.class,
272 JsonNodeFactory.class, LinkedHashMap.class,
273 TextNode.class, BooleanNode.class,
274 LongNode.class, DoubleNode.class, ShortNode.class,
275 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700276
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700277 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700278 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700279 .withSerializer(serializer)
280 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700281 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700282 prefsConsistentMap.addListener(prefsListener);
283 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700284
285 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
286 .withName(ONOS_SESSION_TOKENS)
287 .withSerializer(serializer)
288 .withRelaxedReadConsistency()
289 .build();
290 tokens = tokensConsistentMap.asJavaMap();
291
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800292 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700293
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800294 log.info("Started");
295 }
296
297 @Deactivate
298 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700299 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700300 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700301 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800302 unregister(core);
303 log.info("Stopped");
304 }
305
306 @Override
307 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900308 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800309 if (!extensions.contains(extension)) {
310 extensions.add(extension);
311 for (UiView view : extension.views()) {
312 views.put(view.id(), extension);
313 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700314 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800315 }
316 }
317
318 @Override
319 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900320 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800321 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700322 extension.views().stream()
323 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700324 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800325 }
326
327 @Override
328 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900329 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800330 return ImmutableList.copyOf(extensions);
331 }
332
333 @Override
334 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900335 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800336 return views.get(viewId);
337 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700338
Thomas Vachuska0af26912016-03-21 21:37:30 -0700339 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700340 public synchronized LionBundle getNavLionBundle() {
341 return navLion;
342 }
343
344 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700345 public Set<String> getUserNames() {
346 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
347 prefs.keySet().forEach(k -> builder.add(userName(k)));
348 return builder.build();
349 }
350
351 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700352 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700353 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
354 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700355 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700356 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
357 return builder.build();
358 }
359
360 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700361 public ObjectNode getPreference(String username, String key) {
362 return prefs.get(key(username, key));
363 }
364
365 @Override
366 public void setPreference(String username, String key, ObjectNode value) {
367 prefs.put(key(username, key), value);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700368 }
369
Simon Huntc54cd1b2015-05-11 13:43:44 -0700370 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700371 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700372
373 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700374
375 @Override
376 public Set<String> getNames() {
377 return ImmutableSet.copyOf(sprites.keySet());
378 }
379
380 @Override
381 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700382 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700383 sprites.put(name, spriteData);
384 }
385
386 @Override
387 public JsonNode get(String name) {
388 return sprites.get(name);
389 }
390
Thomas Vachuska0af26912016-03-21 21:37:30 -0700391 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700392 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700393 }
394
Simon Hunt3678c2a2016-03-28 14:48:07 -0700395
Thomas Vachuska0af26912016-03-21 21:37:30 -0700396 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700397 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700398 }
399
400 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700401 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700402 }
403
Simon Hunt1169c952017-06-05 11:20:11 -0700404
405 // =====================================================================
406 // UiTokenService
407
408 @Override
409 public UiSessionToken issueToken(String username) {
410 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
411 tokens.put(token, username);
412 log.debug("UiSessionToken issued: {}", token);
413 return token;
414 }
415
416 @Override
417 public void revokeToken(UiSessionToken token) {
418 if (token != null) {
419 tokens.remove(token);
420 log.debug("UiSessionToken revoked: {}", token);
421 }
422 }
423
424 @Override
425 public boolean isTokenValid(UiSessionToken token) {
426 return token != null && tokens.containsKey(token);
427 }
428
429 private final class SessionTokenGenerator {
430 private final SecureRandom random = new SecureRandom();
431
432 /*
433 This works by choosing 130 bits from a cryptographically secure
434 random bit generator, and encoding them in base-32.
435
436 128 bits is considered to be cryptographically strong, but each
437 digit in a base 32 number can encode 5 bits, so 128 is rounded up
438 to the next multiple of 5.
439
440 This encoding is compact and efficient, with 5 random bits per
441 character. Compare this to a random UUID, which only has 3.4 bits
442 per character in standard layout, and only 122 random bits in total.
443
444 Note that SecureRandom objects are expensive to initialize, so
445 we'll want to keep it around and re-use it.
446 */
447
448 private String nextSessionId() {
449 return new BigInteger(130, random).toString(32);
450 }
451 }
452
Thomas Vachuska0af26912016-03-21 21:37:30 -0700453 // Auxiliary listener to preference map events.
454 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700455 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700456 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700457 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700458 eventHandlingExecutor.execute(() -> {
459 String userName = userName(event.key());
460 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
461 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
462 }
463 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700464 }
465
466 private ObjectNode jsonPrefs() {
467 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700468 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700469 return json;
470 }
471 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800472}