blob: 26692c96a474ff222168a469e6b5ad5c4bb5d49e [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;
Simon Huntd6d3ad32017-06-21 15:27:06 -070072import java.util.Locale;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080073import java.util.Map;
Thomas Vachuska9ed335b2015-04-14 12:07:47 -070074import java.util.Set;
Madan Jampanibf8ee802016-05-04 14:07:36 -070075import java.util.concurrent.ExecutorService;
76import java.util.concurrent.Executors;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080077
78import static com.google.common.collect.ImmutableList.of;
79import static java.util.stream.Collectors.toSet;
Heedo Kang4a47a302016-02-29 17:40:23 +090080import static org.onosproject.security.AppGuard.checkPermission;
81import static org.onosproject.security.AppPermission.Type.UI_READ;
82import static org.onosproject.security.AppPermission.Type.UI_WRITE;
Thomas Vachuska0af26912016-03-21 21:37:30 -070083import static org.onosproject.ui.UiView.Category.NETWORK;
84import static org.onosproject.ui.UiView.Category.PLATFORM;
Simon Huntd8754652017-06-21 11:45:22 -070085import static org.onosproject.ui.impl.lion.BundleStitcher.generateBundles;
Heedo Kang4a47a302016-02-29 17:40:23 +090086
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080087/**
88 * Manages the user interface extensions.
89 */
90@Component(immediate = true)
91@Service
Simon Hunt3678c2a2016-03-28 14:48:07 -070092public class UiExtensionManager
Simon Hunt1169c952017-06-05 11:20:11 -070093 implements UiExtensionService, UiPreferencesService, SpriteService,
94 UiTokenService {
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080095
Thomas Vachuskafa74dd72016-03-20 19:11:12 -070096 private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
Simon Hunt3678c2a2016-03-28 14:48:07 -070097
Madan Jampani7b93ceb2016-05-04 09:58:40 -070098 private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
Simon Hunt1169c952017-06-05 11:20:11 -070099 private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
Simon Hunte05cae42015-07-23 17:35:24 -0700100 private static final String CORE = "core";
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700101 private static final String GUI_ADDED = "guiAdded";
102 private static final String GUI_REMOVED = "guiRemoved";
Thomas Vachuska0af26912016-03-21 21:37:30 -0700103 private static final String UPDATE_PREFS = "updatePrefs";
Simon Hunt3678c2a2016-03-28 14:48:07 -0700104 private static final String SLASH = "/";
105
106 private static final int IDX_USER = 0;
107 private static final int IDX_KEY = 1;
Simon Hunte05cae42015-07-23 17:35:24 -0700108
Simon Hunt7379a3d2017-06-20 16:50:39 -0700109 private static final String LION_BASE = "/org/onosproject/ui/lion";
110
111 private static final String[] LION_TAGS = {
112 "core.view.Cluster",
113
114 // TODO: fill this out, once we have written the other config files
115 };
116
117
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800118 private final Logger log = LoggerFactory.getLogger(getClass());
119
Simon Huntd6d3ad32017-06-21 15:27:06 -0700120 // First thing to do is to set the locale (before creating core extension).
121 private final Locale runtimeLocale = LionUtils.setupRuntimeLocale();
122
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800123 // List of all extensions
124 private final List<UiExtension> extensions = Lists.newArrayList();
125
126 // Map of views to extensions
127 private final Map<String, UiExtension> views = Maps.newHashMap();
128
129 // Core views & core extension
Thomas Vachuska3553b302015-03-07 14:49:43 -0800130 private final UiExtension core = createCoreExtension();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800131
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700132 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
133 protected MastershipService mastershipService;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800134
Thomas Vachuska0af26912016-03-21 21:37:30 -0700135 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
136 protected StorageService storageService;
137
138 // User preferences
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700139 private ConsistentMap<String, ObjectNode> prefsConsistentMap;
140 private Map<String, ObjectNode> prefs;
141 private final MapEventListener<String, ObjectNode> prefsListener =
Thomas Vachuska0af26912016-03-21 21:37:30 -0700142 new InternalPrefsListener();
143
Simon Hunt1169c952017-06-05 11:20:11 -0700144 // Session tokens
145 private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
146 private Map<UiSessionToken, String> tokens;
147 private final SessionTokenGenerator tokenGen =
148 new SessionTokenGenerator();
149
Thomas Vachuska0af26912016-03-21 21:37:30 -0700150 private final ObjectMapper mapper = new ObjectMapper();
151
Madan Jampanibf8ee802016-05-04 14:07:36 -0700152 private final ExecutorService eventHandlingExecutor =
Simon Huntd5b96732016-07-08 13:22:27 -0700153 Executors.newSingleThreadExecutor(
154 Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Madan Jampanibf8ee802016-05-04 14:07:36 -0700155
Thomas Vachuska3553b302015-03-07 14:49:43 -0800156 // Creates core UI extension
Simon Huntc54cd1b2015-05-11 13:43:44 -0700157 private UiExtension createCoreExtension() {
Simon Hunt20e16792015-04-24 14:29:39 -0700158 List<UiView> coreViews = of(
159 new UiView(PLATFORM, "app", "Applications", "nav_apps"),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700160 new UiView(PLATFORM, "settings", "Settings", "nav_settings"),
Simon Hunt20e16792015-04-24 14:29:39 -0700161 new UiView(PLATFORM, "cluster", "Cluster Nodes", "nav_cluster"),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700162 new UiView(PLATFORM, "processor", "Packet Processors", "nav_processors"),
chengfanc5a99dc2017-01-08 19:28:29 +0800163 new UiView(PLATFORM, "partition", "Partitions", "nav_partitions"),
Simon Hunt20e16792015-04-24 14:29:39 -0700164 new UiView(NETWORK, "topo", "Topology", "nav_topo"),
Simon Hunt47f8fb72017-04-11 13:49:31 -0700165 new UiView(NETWORK, "topo2", "Topology 2", "nav_topo2"),
Simon Hunt20e16792015-04-24 14:29:39 -0700166 new UiView(NETWORK, "device", "Devices", "nav_devs"),
167 new UiViewHidden("flow"),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700168 new UiViewHidden("port"),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700169 new UiViewHidden("group"),
Jian Li1f544732015-12-30 23:36:37 -0800170 new UiViewHidden("meter"),
Simon Hunt20e16792015-04-24 14:29:39 -0700171 new UiView(NETWORK, "link", "Links", "nav_links"),
172 new UiView(NETWORK, "host", "Hosts", "nav_hosts"),
chengfanb466a7e2015-08-21 09:59:29 -0500173 new UiView(NETWORK, "intent", "Intents", "nav_intents"),
Simon Hunt986b92f2016-06-03 15:46:59 -0700174 new UiView(NETWORK, "tunnel", "Tunnels", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700175 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700176
Simon Hunt4c7edd32015-03-11 10:42:53 -0700177 UiMessageHandlerFactory messageHandlerFactory =
178 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700179 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700180 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700181 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700182 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700183 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700184 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700185 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700186 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700187 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700188 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700189 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800190 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700191 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700192 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700193 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500194 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700195 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800196 new TunnelViewMessageHandler(),
197 new PartitionViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700198 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700199
Simon Hunte05cae42015-07-23 17:35:24 -0700200 UiTopoOverlayFactory topoOverlayFactory =
201 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800202 new TrafficOverlay(),
203 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700204 );
205
Simon Hunt6cc86452017-04-27 17:46:22 -0700206 UiTopo2OverlayFactory topo2OverlayFactory =
207 () -> ImmutableList.of(
208 new Traffic2Overlay()
209 );
210
Steven Burrows3a9a6442016-05-05 15:31:16 +0100211 UiTopoMapFactory topoMapFactory =
212 () -> ImmutableList.of(
213 new UiTopoMap("australia", "Australia", "*australia", 1.0),
214 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
215 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
216 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700217 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100218 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700219 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100220 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700221 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100222 new UiTopoMap("japan", "Japan", "*japan", 0.8),
223 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
224 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
225 new UiTopoMap("africa", "Africa", "*africa", 0.7),
226 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
227 new UiTopoMap("asia", "Asia", "*asia", 0.7)
228 );
229
Simon Hunte05cae42015-07-23 17:35:24 -0700230 return new UiExtension.Builder(CL, coreViews)
Simon Huntd8754652017-06-21 11:45:22 -0700231 .lionBundles(generateBundles(LION_BASE, LION_TAGS))
Simon Hunte05cae42015-07-23 17:35:24 -0700232 .messageHandlerFactory(messageHandlerFactory)
233 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700234 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100235 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700236 .resourcePath(CORE)
237 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800238 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800239
Simon Hunt7379a3d2017-06-20 16:50:39 -0700240
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800241 @Activate
242 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700243 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700244 ObjectNode.class, ArrayNode.class,
245 JsonNodeFactory.class, LinkedHashMap.class,
246 TextNode.class, BooleanNode.class,
247 LongNode.class, DoubleNode.class, ShortNode.class,
248 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700249
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700250 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700251 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700252 .withSerializer(serializer)
253 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700254 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700255 prefsConsistentMap.addListener(prefsListener);
256 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700257
258 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
259 .withName(ONOS_SESSION_TOKENS)
260 .withSerializer(serializer)
261 .withRelaxedReadConsistency()
262 .build();
263 tokens = tokensConsistentMap.asJavaMap();
264
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800265 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700266
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800267 log.info("Started");
268 }
269
270 @Deactivate
271 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700272 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700273 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700274 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800275 unregister(core);
276 log.info("Stopped");
277 }
278
279 @Override
280 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900281 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800282 if (!extensions.contains(extension)) {
283 extensions.add(extension);
284 for (UiView view : extension.views()) {
285 views.put(view.id(), extension);
286 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700287 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800288 }
289 }
290
291 @Override
292 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900293 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800294 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700295 extension.views().stream()
296 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700297 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800298 }
299
300 @Override
301 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900302 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800303 return ImmutableList.copyOf(extensions);
304 }
305
306 @Override
307 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900308 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800309 return views.get(viewId);
310 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700311
Thomas Vachuska0af26912016-03-21 21:37:30 -0700312 @Override
313 public Set<String> getUserNames() {
314 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
315 prefs.keySet().forEach(k -> builder.add(userName(k)));
316 return builder.build();
317 }
318
319 @Override
320 public Map<String, ObjectNode> getPreferences(String userName) {
321 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
322 prefs.entrySet().stream()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700323 .filter(e -> e.getKey().startsWith(userName + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700324 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
325 return builder.build();
326 }
327
328 @Override
329 public void setPreference(String userName, String preference, ObjectNode value) {
330 prefs.put(key(userName, preference), value);
331 }
332
Simon Huntc54cd1b2015-05-11 13:43:44 -0700333 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700334 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700335
336 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700337
338 @Override
339 public Set<String> getNames() {
340 return ImmutableSet.copyOf(sprites.keySet());
341 }
342
343 @Override
344 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700345 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700346 sprites.put(name, spriteData);
347 }
348
349 @Override
350 public JsonNode get(String name) {
351 return sprites.get(name);
352 }
353
Thomas Vachuska0af26912016-03-21 21:37:30 -0700354 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700355 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700356 }
357
Simon Hunt3678c2a2016-03-28 14:48:07 -0700358
Thomas Vachuska0af26912016-03-21 21:37:30 -0700359 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700360 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700361 }
362
363 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700364 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700365 }
366
Simon Hunt1169c952017-06-05 11:20:11 -0700367
368 // =====================================================================
369 // UiTokenService
370
371 @Override
372 public UiSessionToken issueToken(String username) {
373 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
374 tokens.put(token, username);
375 log.debug("UiSessionToken issued: {}", token);
376 return token;
377 }
378
379 @Override
380 public void revokeToken(UiSessionToken token) {
381 if (token != null) {
382 tokens.remove(token);
383 log.debug("UiSessionToken revoked: {}", token);
384 }
385 }
386
387 @Override
388 public boolean isTokenValid(UiSessionToken token) {
389 return token != null && tokens.containsKey(token);
390 }
391
392 private final class SessionTokenGenerator {
393 private final SecureRandom random = new SecureRandom();
394
395 /*
396 This works by choosing 130 bits from a cryptographically secure
397 random bit generator, and encoding them in base-32.
398
399 128 bits is considered to be cryptographically strong, but each
400 digit in a base 32 number can encode 5 bits, so 128 is rounded up
401 to the next multiple of 5.
402
403 This encoding is compact and efficient, with 5 random bits per
404 character. Compare this to a random UUID, which only has 3.4 bits
405 per character in standard layout, and only 122 random bits in total.
406
407 Note that SecureRandom objects are expensive to initialize, so
408 we'll want to keep it around and re-use it.
409 */
410
411 private String nextSessionId() {
412 return new BigInteger(130, random).toString(32);
413 }
414 }
415
Thomas Vachuska0af26912016-03-21 21:37:30 -0700416 // Auxiliary listener to preference map events.
417 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700418 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700419 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700420 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700421 eventHandlingExecutor.execute(() -> {
422 String userName = userName(event.key());
423 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
424 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
425 }
426 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700427 }
428
429 private ObjectNode jsonPrefs() {
430 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700431 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700432 return json;
433 }
434 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800435}