blob: 85935de11edbc2a1b6ae57fbc89f3084cb86abc9 [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 Huntb8042032017-06-13 15:03:23 -070064import org.onosproject.ui.lion.LionUtils;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080065import org.slf4j.Logger;
66import org.slf4j.LoggerFactory;
67
Simon Hunt1169c952017-06-05 11:20:11 -070068import java.math.BigInteger;
69import java.security.SecureRandom;
Thomas Vachuska0af26912016-03-21 21:37:30 -070070import java.util.LinkedHashMap;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080071import java.util.List;
72import java.util.Map;
Thomas Vachuska9ed335b2015-04-14 12:07:47 -070073import java.util.Set;
Madan Jampanibf8ee802016-05-04 14:07:36 -070074import java.util.concurrent.ExecutorService;
75import java.util.concurrent.Executors;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080076
77import static com.google.common.collect.ImmutableList.of;
78import static java.util.stream.Collectors.toSet;
Heedo Kang4a47a302016-02-29 17:40:23 +090079import static org.onosproject.security.AppGuard.checkPermission;
80import static org.onosproject.security.AppPermission.Type.UI_READ;
81import static org.onosproject.security.AppPermission.Type.UI_WRITE;
Thomas Vachuska0af26912016-03-21 21:37:30 -070082import static org.onosproject.ui.UiView.Category.NETWORK;
83import static org.onosproject.ui.UiView.Category.PLATFORM;
Heedo Kang4a47a302016-02-29 17:40:23 +090084
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080085/**
86 * Manages the user interface extensions.
87 */
88@Component(immediate = true)
89@Service
Simon Hunt3678c2a2016-03-28 14:48:07 -070090public class UiExtensionManager
Simon Hunt1169c952017-06-05 11:20:11 -070091 implements UiExtensionService, UiPreferencesService, SpriteService,
92 UiTokenService {
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080093
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070094 private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
Simon Hunt3678c2a2016-03-28 14:48:07 -070095
Madan Jampani7b93ceb2016-05-04 09:58:40 -070096 private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
Simon Hunt1169c952017-06-05 11:20:11 -070097 private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
Simon Hunte05cae42015-07-23 17:35:24 -070098 private static final String CORE = "core";
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070099 private static final String GUI_ADDED = "guiAdded";
100 private static final String GUI_REMOVED = "guiRemoved";
Thomas Vachuska0af26912016-03-21 21:37:30 -0700101 private static final String UPDATE_PREFS = "updatePrefs";
Simon Hunt3678c2a2016-03-28 14:48:07 -0700102 private static final String SLASH = "/";
103
104 private static final int IDX_USER = 0;
105 private static final int IDX_KEY = 1;
Simon Hunte05cae42015-07-23 17:35:24 -0700106
Simon Hunt7379a3d2017-06-20 16:50:39 -0700107 private static final String LION_BASE = "/org/onosproject/ui/lion";
108
109 private static final String[] LION_TAGS = {
110 "core.view.Cluster",
111
112 // TODO: fill this out, once we have written the other config files
113 };
114
115
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800116 private final Logger log = LoggerFactory.getLogger(getClass());
117
118 // List of all extensions
119 private final List<UiExtension> extensions = Lists.newArrayList();
120
121 // Map of views to extensions
122 private final Map<String, UiExtension> views = Maps.newHashMap();
123
124 // Core views & core extension
Thomas Vachuska3553b302015-03-07 14:49:43 -0800125 private final UiExtension core = createCoreExtension();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800126
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700127 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
128 protected MastershipService mastershipService;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800129
Thomas Vachuska0af26912016-03-21 21:37:30 -0700130 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
131 protected StorageService storageService;
132
133 // User preferences
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700134 private ConsistentMap<String, ObjectNode> prefsConsistentMap;
135 private Map<String, ObjectNode> prefs;
136 private final MapEventListener<String, ObjectNode> prefsListener =
Thomas Vachuska0af26912016-03-21 21:37:30 -0700137 new InternalPrefsListener();
138
Simon Hunt1169c952017-06-05 11:20:11 -0700139 // Session tokens
140 private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
141 private Map<UiSessionToken, String> tokens;
142 private final SessionTokenGenerator tokenGen =
143 new SessionTokenGenerator();
144
Thomas Vachuska0af26912016-03-21 21:37:30 -0700145 private final ObjectMapper mapper = new ObjectMapper();
146
Madan Jampanibf8ee802016-05-04 14:07:36 -0700147 private final ExecutorService eventHandlingExecutor =
Simon Huntd5b96732016-07-08 13:22:27 -0700148 Executors.newSingleThreadExecutor(
149 Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Madan Jampanibf8ee802016-05-04 14:07:36 -0700150
Thomas Vachuska3553b302015-03-07 14:49:43 -0800151 // Creates core UI extension
Simon Huntc54cd1b2015-05-11 13:43:44 -0700152 private UiExtension createCoreExtension() {
Simon Hunt20e16792015-04-24 14:29:39 -0700153 List<UiView> coreViews = of(
154 new UiView(PLATFORM, "app", "Applications", "nav_apps"),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700155 new UiView(PLATFORM, "settings", "Settings", "nav_settings"),
Simon Hunt20e16792015-04-24 14:29:39 -0700156 new UiView(PLATFORM, "cluster", "Cluster Nodes", "nav_cluster"),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700157 new UiView(PLATFORM, "processor", "Packet Processors", "nav_processors"),
chengfanc5a99dc2017-01-08 19:28:29 +0800158 new UiView(PLATFORM, "partition", "Partitions", "nav_partitions"),
Simon Hunt20e16792015-04-24 14:29:39 -0700159 new UiView(NETWORK, "topo", "Topology", "nav_topo"),
Simon Hunt47f8fb72017-04-11 13:49:31 -0700160 new UiView(NETWORK, "topo2", "Topology 2", "nav_topo2"),
Simon Hunt20e16792015-04-24 14:29:39 -0700161 new UiView(NETWORK, "device", "Devices", "nav_devs"),
162 new UiViewHidden("flow"),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700163 new UiViewHidden("port"),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700164 new UiViewHidden("group"),
Jian Li1f544732015-12-30 23:36:37 -0800165 new UiViewHidden("meter"),
Simon Hunt20e16792015-04-24 14:29:39 -0700166 new UiView(NETWORK, "link", "Links", "nav_links"),
167 new UiView(NETWORK, "host", "Hosts", "nav_hosts"),
chengfanb466a7e2015-08-21 09:59:29 -0500168 new UiView(NETWORK, "intent", "Intents", "nav_intents"),
Simon Hunt986b92f2016-06-03 15:46:59 -0700169 new UiView(NETWORK, "tunnel", "Tunnels", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700170 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700171
Simon Hunt4c7edd32015-03-11 10:42:53 -0700172 UiMessageHandlerFactory messageHandlerFactory =
173 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700174 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700175 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700176 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700177 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700178 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700179 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700180 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700181 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700182 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700183 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700184 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800185 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700186 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700187 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700188 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500189 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700190 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800191 new TunnelViewMessageHandler(),
192 new PartitionViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700193 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700194
Simon Hunte05cae42015-07-23 17:35:24 -0700195 UiTopoOverlayFactory topoOverlayFactory =
196 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800197 new TrafficOverlay(),
198 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700199 );
200
Simon Hunt6cc86452017-04-27 17:46:22 -0700201 UiTopo2OverlayFactory topo2OverlayFactory =
202 () -> ImmutableList.of(
203 new Traffic2Overlay()
204 );
205
Steven Burrows3a9a6442016-05-05 15:31:16 +0100206 UiTopoMapFactory topoMapFactory =
207 () -> ImmutableList.of(
208 new UiTopoMap("australia", "Australia", "*australia", 1.0),
209 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
210 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
211 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700212 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100213 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700214 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100215 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700216 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100217 new UiTopoMap("japan", "Japan", "*japan", 0.8),
218 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
219 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
220 new UiTopoMap("africa", "Africa", "*africa", 0.7),
221 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
222 new UiTopoMap("asia", "Asia", "*asia", 0.7)
223 );
224
Simon Hunte05cae42015-07-23 17:35:24 -0700225 return new UiExtension.Builder(CL, coreViews)
Simon Hunt7379a3d2017-06-20 16:50:39 -0700226 // TODO: currently broken, until BundleStitcher & LionConfig
227 // have been moved to web.gui module...
228// .lionBundles(generateLionBundles(LION_BASE, LION_TAGS))
Simon Hunte05cae42015-07-23 17:35:24 -0700229 .messageHandlerFactory(messageHandlerFactory)
230 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700231 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100232 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700233 .resourcePath(CORE)
234 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800235 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800236
Simon Hunt7379a3d2017-06-20 16:50:39 -0700237
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800238 @Activate
239 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700240 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Hunt7379a3d2017-06-20 16:50:39 -0700241 ObjectNode.class, ArrayNode.class,
242 JsonNodeFactory.class, LinkedHashMap.class,
243 TextNode.class, BooleanNode.class,
244 LongNode.class, DoubleNode.class, ShortNode.class,
245 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700246
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700247 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700248 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700249 .withSerializer(serializer)
250 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700251 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700252 prefsConsistentMap.addListener(prefsListener);
253 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700254
255 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
256 .withName(ONOS_SESSION_TOKENS)
257 .withSerializer(serializer)
258 .withRelaxedReadConsistency()
259 .build();
260 tokens = tokensConsistentMap.asJavaMap();
261
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800262 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700263 LionUtils.setupRuntimeLocale();
264
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800265 log.info("Started");
266 }
267
268 @Deactivate
269 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700270 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700271 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700272 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800273 unregister(core);
274 log.info("Stopped");
275 }
276
277 @Override
278 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900279 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800280 if (!extensions.contains(extension)) {
281 extensions.add(extension);
282 for (UiView view : extension.views()) {
283 views.put(view.id(), extension);
284 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700285 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800286 }
287 }
288
289 @Override
290 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900291 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800292 extensions.remove(extension);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700293 extension.views().stream().map(UiView::id).collect(toSet()).forEach(views::remove);
294 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800295 }
296
297 @Override
298 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900299 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800300 return ImmutableList.copyOf(extensions);
301 }
302
303 @Override
304 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900305 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800306 return views.get(viewId);
307 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700308
Thomas Vachuska0af26912016-03-21 21:37:30 -0700309 @Override
310 public Set<String> getUserNames() {
311 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
312 prefs.keySet().forEach(k -> builder.add(userName(k)));
313 return builder.build();
314 }
315
316 @Override
317 public Map<String, ObjectNode> getPreferences(String userName) {
318 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
319 prefs.entrySet().stream()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700320 .filter(e -> e.getKey().startsWith(userName + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700321 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
322 return builder.build();
323 }
324
325 @Override
326 public void setPreference(String userName, String preference, ObjectNode value) {
327 prefs.put(key(userName, preference), value);
328 }
329
Simon Huntc54cd1b2015-05-11 13:43:44 -0700330 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700331 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700332
333 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700334
335 @Override
336 public Set<String> getNames() {
337 return ImmutableSet.copyOf(sprites.keySet());
338 }
339
340 @Override
341 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700342 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700343 sprites.put(name, spriteData);
344 }
345
346 @Override
347 public JsonNode get(String name) {
348 return sprites.get(name);
349 }
350
Thomas Vachuska0af26912016-03-21 21:37:30 -0700351 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700352 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700353 }
354
Simon Hunt3678c2a2016-03-28 14:48:07 -0700355
Thomas Vachuska0af26912016-03-21 21:37:30 -0700356 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700357 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700358 }
359
360 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700361 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700362 }
363
Simon Hunt1169c952017-06-05 11:20:11 -0700364
365 // =====================================================================
366 // UiTokenService
367
368 @Override
369 public UiSessionToken issueToken(String username) {
370 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
371 tokens.put(token, username);
372 log.debug("UiSessionToken issued: {}", token);
373 return token;
374 }
375
376 @Override
377 public void revokeToken(UiSessionToken token) {
378 if (token != null) {
379 tokens.remove(token);
380 log.debug("UiSessionToken revoked: {}", token);
381 }
382 }
383
384 @Override
385 public boolean isTokenValid(UiSessionToken token) {
386 return token != null && tokens.containsKey(token);
387 }
388
389 private final class SessionTokenGenerator {
390 private final SecureRandom random = new SecureRandom();
391
392 /*
393 This works by choosing 130 bits from a cryptographically secure
394 random bit generator, and encoding them in base-32.
395
396 128 bits is considered to be cryptographically strong, but each
397 digit in a base 32 number can encode 5 bits, so 128 is rounded up
398 to the next multiple of 5.
399
400 This encoding is compact and efficient, with 5 random bits per
401 character. Compare this to a random UUID, which only has 3.4 bits
402 per character in standard layout, and only 122 random bits in total.
403
404 Note that SecureRandom objects are expensive to initialize, so
405 we'll want to keep it around and re-use it.
406 */
407
408 private String nextSessionId() {
409 return new BigInteger(130, random).toString(32);
410 }
411 }
412
Thomas Vachuska0af26912016-03-21 21:37:30 -0700413 // Auxiliary listener to preference map events.
414 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700415 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700416 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700417 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700418 eventHandlingExecutor.execute(() -> {
419 String userName = userName(event.key());
420 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
421 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
422 }
423 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700424 }
425
426 private ObjectNode jsonPrefs() {
427 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700428 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700429 return json;
430 }
431 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800432}