blob: a50fd1be33872f75ce9b199f921c415903a9d124 [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;
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"),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700197 new UiViewHidden("pipeconf"),
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700198
199 mkView(NETWORK, "link", "nav_links"),
200 mkView(NETWORK, "host", "nav_hosts"),
201 mkView(NETWORK, "intent", "nav_intents"),
202 mkView(NETWORK, "tunnel", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700203 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700204
Simon Hunt4c7edd32015-03-11 10:42:53 -0700205 UiMessageHandlerFactory messageHandlerFactory =
206 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700207 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700208 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700209 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700210 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700211 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700212 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700213 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700214 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700215 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700216 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700217 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800218 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700219 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700220 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700221 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500222 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700223 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800224 new TunnelViewMessageHandler(),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700225 new PartitionViewMessageHandler(),
226 new PipeconfViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700227 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700228
Simon Hunte05cae42015-07-23 17:35:24 -0700229 UiTopoOverlayFactory topoOverlayFactory =
230 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800231 new TrafficOverlay(),
232 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700233 );
234
Simon Hunt6cc86452017-04-27 17:46:22 -0700235 UiTopo2OverlayFactory topo2OverlayFactory =
236 () -> ImmutableList.of(
237 new Traffic2Overlay()
238 );
239
Steven Burrows3a9a6442016-05-05 15:31:16 +0100240 UiTopoMapFactory topoMapFactory =
241 () -> ImmutableList.of(
242 new UiTopoMap("australia", "Australia", "*australia", 1.0),
243 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
244 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
245 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700246 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100247 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700248 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100249 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700250 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100251 new UiTopoMap("japan", "Japan", "*japan", 0.8),
252 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
253 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
254 new UiTopoMap("africa", "Africa", "*africa", 0.7),
255 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
256 new UiTopoMap("asia", "Asia", "*asia", 0.7)
257 );
258
Simon Hunte05cae42015-07-23 17:35:24 -0700259 return new UiExtension.Builder(CL, coreViews)
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700260 .lionBundles(lionBundles)
Simon Hunte05cae42015-07-23 17:35:24 -0700261 .messageHandlerFactory(messageHandlerFactory)
262 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700263 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100264 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700265 .resourcePath(CORE)
266 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800267 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800268
Simon Hunt7379a3d2017-06-20 16:50:39 -0700269
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800270 @Activate
271 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700272 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700273 ObjectNode.class, ArrayNode.class,
274 JsonNodeFactory.class, LinkedHashMap.class,
275 TextNode.class, BooleanNode.class,
276 LongNode.class, DoubleNode.class, ShortNode.class,
277 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700278
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700279 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700280 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700281 .withSerializer(serializer)
282 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700283 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700284 prefsConsistentMap.addListener(prefsListener);
285 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700286
287 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
288 .withName(ONOS_SESSION_TOKENS)
289 .withSerializer(serializer)
290 .withRelaxedReadConsistency()
291 .build();
292 tokens = tokensConsistentMap.asJavaMap();
293
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800294 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700295
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800296 log.info("Started");
297 }
298
299 @Deactivate
300 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700301 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700302 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700303 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800304 unregister(core);
305 log.info("Stopped");
306 }
307
308 @Override
309 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900310 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800311 if (!extensions.contains(extension)) {
312 extensions.add(extension);
313 for (UiView view : extension.views()) {
314 views.put(view.id(), extension);
315 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700316 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800317 }
318 }
319
320 @Override
321 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900322 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800323 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700324 extension.views().stream()
325 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700326 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800327 }
328
329 @Override
330 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900331 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800332 return ImmutableList.copyOf(extensions);
333 }
334
335 @Override
336 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900337 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800338 return views.get(viewId);
339 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700340
Thomas Vachuska0af26912016-03-21 21:37:30 -0700341 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700342 public synchronized LionBundle getNavLionBundle() {
343 return navLion;
344 }
345
346 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700347 public Set<String> getUserNames() {
348 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
349 prefs.keySet().forEach(k -> builder.add(userName(k)));
350 return builder.build();
351 }
352
353 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700354 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700355 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
356 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700357 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700358 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
359 return builder.build();
360 }
361
362 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700363 public ObjectNode getPreference(String username, String key) {
364 return prefs.get(key(username, key));
365 }
366
367 @Override
368 public void setPreference(String username, String key, ObjectNode value) {
Thomas Vachuska99b7bbe2018-02-01 15:29:46 -0800369 if (value != null) {
370 prefs.put(key(username, key), value);
371 } else {
372 prefs.remove(key(username, key));
373 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700374 }
375
Simon Huntc54cd1b2015-05-11 13:43:44 -0700376 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700377 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700378
379 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700380
381 @Override
382 public Set<String> getNames() {
383 return ImmutableSet.copyOf(sprites.keySet());
384 }
385
386 @Override
387 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700388 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700389 sprites.put(name, spriteData);
390 }
391
392 @Override
393 public JsonNode get(String name) {
394 return sprites.get(name);
395 }
396
Thomas Vachuska0af26912016-03-21 21:37:30 -0700397 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700398 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700399 }
400
Simon Hunt3678c2a2016-03-28 14:48:07 -0700401
Thomas Vachuska0af26912016-03-21 21:37:30 -0700402 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700403 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700404 }
405
406 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700407 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700408 }
409
Simon Hunt1169c952017-06-05 11:20:11 -0700410
411 // =====================================================================
412 // UiTokenService
413
414 @Override
415 public UiSessionToken issueToken(String username) {
416 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
417 tokens.put(token, username);
418 log.debug("UiSessionToken issued: {}", token);
419 return token;
420 }
421
422 @Override
423 public void revokeToken(UiSessionToken token) {
424 if (token != null) {
425 tokens.remove(token);
426 log.debug("UiSessionToken revoked: {}", token);
427 }
428 }
429
430 @Override
431 public boolean isTokenValid(UiSessionToken token) {
432 return token != null && tokens.containsKey(token);
433 }
434
435 private final class SessionTokenGenerator {
436 private final SecureRandom random = new SecureRandom();
437
438 /*
439 This works by choosing 130 bits from a cryptographically secure
440 random bit generator, and encoding them in base-32.
441
442 128 bits is considered to be cryptographically strong, but each
443 digit in a base 32 number can encode 5 bits, so 128 is rounded up
444 to the next multiple of 5.
445
446 This encoding is compact and efficient, with 5 random bits per
447 character. Compare this to a random UUID, which only has 3.4 bits
448 per character in standard layout, and only 122 random bits in total.
449
450 Note that SecureRandom objects are expensive to initialize, so
451 we'll want to keep it around and re-use it.
452 */
453
454 private String nextSessionId() {
455 return new BigInteger(130, random).toString(32);
456 }
457 }
458
Thomas Vachuska0af26912016-03-21 21:37:30 -0700459 // Auxiliary listener to preference map events.
460 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700461 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700462 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700463 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700464 eventHandlingExecutor.execute(() -> {
465 String userName = userName(event.key());
466 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
467 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
468 }
469 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700470 }
471
472 private ObjectNode jsonPrefs() {
473 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700474 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700475 return json;
476 }
477 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800478}