blob: 7d5c32484d625bf3988c4f3afe5507cafceb0308 [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;
Madan Jampanibf8ee802016-05-04 14:07:36 -070035import org.onlab.util.Tools;
Thomas Vachuska51f540f2015-05-27 17:26:57 -070036import org.onosproject.mastership.MastershipService;
Thomas Vachuska0af26912016-03-21 21:37:30 -070037import org.onosproject.store.serializers.KryoNamespaces;
Madan Jampani7b93ceb2016-05-04 09:58:40 -070038import org.onosproject.store.service.ConsistentMap;
39import org.onosproject.store.service.MapEvent;
40import org.onosproject.store.service.MapEventListener;
41import org.onosproject.store.service.Serializer;
Thomas Vachuska0af26912016-03-21 21:37:30 -070042import org.onosproject.store.service.StorageService;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080043import org.onosproject.ui.UiExtension;
44import org.onosproject.ui.UiExtensionService;
Laszlo Papp759f0d32018-03-05 13:24:30 +000045import org.onosproject.ui.UiGlyph;
46import org.onosproject.ui.UiGlyphFactory;
Thomas Vachuska3553b302015-03-07 14:49:43 -080047import org.onosproject.ui.UiMessageHandlerFactory;
Thomas Vachuska0af26912016-03-21 21:37:30 -070048import org.onosproject.ui.UiPreferencesService;
Simon Hunt1169c952017-06-05 11:20:11 -070049import org.onosproject.ui.UiSessionToken;
50import org.onosproject.ui.UiTokenService;
Simon Hunt6cc86452017-04-27 17:46:22 -070051import org.onosproject.ui.UiTopo2OverlayFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070052import org.onosproject.ui.UiTopoMap;
Steven Burrows3a9a6442016-05-05 15:31:16 +010053import org.onosproject.ui.UiTopoMapFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070054import org.onosproject.ui.UiTopoOverlayFactory;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080055import org.onosproject.ui.UiView;
Simon Hunt1002cd82015-04-23 14:44:03 -070056import org.onosproject.ui.UiViewHidden;
Simon Hunte6f64612017-04-28 00:01:48 -070057import org.onosproject.ui.impl.topo.Topo2TrafficMessageHandler;
Simon Huntd5b96732016-07-08 13:22:27 -070058import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
Simon Hunt6cc86452017-04-27 17:46:22 -070059import org.onosproject.ui.impl.topo.Traffic2Overlay;
Thomas Vachuska1b1355d2018-02-06 16:53:58 -080060import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
Simon Hunt23f9c7b2017-07-10 20:00:30 -070061import org.onosproject.ui.lion.LionBundle;
Simon Huntb8042032017-06-13 15:03:23 -070062import org.onosproject.ui.lion.LionUtils;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070063import org.osgi.service.component.annotations.Activate;
64import org.osgi.service.component.annotations.Component;
65import org.osgi.service.component.annotations.Deactivate;
66import org.osgi.service.component.annotations.Reference;
67import org.osgi.service.component.annotations.ReferenceCardinality;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080068import org.slf4j.Logger;
69import org.slf4j.LoggerFactory;
70
Simon Hunt1169c952017-06-05 11:20:11 -070071import java.math.BigInteger;
72import java.security.SecureRandom;
Thomas Vachuska0af26912016-03-21 21:37:30 -070073import java.util.LinkedHashMap;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080074import java.util.List;
Simon Huntd6d3ad32017-06-21 15:27:06 -070075import java.util.Locale;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080076import java.util.Map;
Thomas Vachuska9ed335b2015-04-14 12:07:47 -070077import java.util.Set;
Madan Jampanibf8ee802016-05-04 14:07:36 -070078import java.util.concurrent.ExecutorService;
79import java.util.concurrent.Executors;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080080
81import static com.google.common.collect.ImmutableList.of;
82import static java.util.stream.Collectors.toSet;
Heedo Kang4a47a302016-02-29 17:40:23 +090083import static org.onosproject.security.AppGuard.checkPermission;
Laszlo Papp759f0d32018-03-05 13:24:30 +000084import static org.onosproject.security.AppPermission.Type.GLYPH_READ;
85import static org.onosproject.security.AppPermission.Type.GLYPH_WRITE;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070086import static org.onosproject.security.AppPermission.Type.UI_READ;
87import static org.onosproject.security.AppPermission.Type.UI_WRITE;
Thomas Vachuska0af26912016-03-21 21:37:30 -070088import static org.onosproject.ui.UiView.Category.NETWORK;
89import static org.onosproject.ui.UiView.Category.PLATFORM;
Simon Huntd8754652017-06-21 11:45:22 -070090import static org.onosproject.ui.impl.lion.BundleStitcher.generateBundles;
Heedo Kang4a47a302016-02-29 17:40:23 +090091
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080092/**
93 * Manages the user interface extensions.
94 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070095@Component(immediate = true, service = { UiExtensionService.class, UiPreferencesService.class, SpriteService.class,
96 UiTokenService.class })
Simon Hunt3678c2a2016-03-28 14:48:07 -070097public class UiExtensionManager
Simon Hunt1169c952017-06-05 11:20:11 -070098 implements UiExtensionService, UiPreferencesService, SpriteService,
99 UiTokenService {
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800100
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700101 private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
Simon Hunt3678c2a2016-03-28 14:48:07 -0700102
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700103 private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
Simon Hunt1169c952017-06-05 11:20:11 -0700104 private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
Simon Hunte05cae42015-07-23 17:35:24 -0700105 private static final String CORE = "core";
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700106 private static final String GUI_ADDED = "guiAdded";
107 private static final String GUI_REMOVED = "guiRemoved";
Laszlo Papp759f0d32018-03-05 13:24:30 +0000108 private static final String GLYPH_ADDED = "glyphAdded";
109 private static final String GLYPH_REMOVED = "glyphRemoved";
Thomas Vachuska0af26912016-03-21 21:37:30 -0700110 private static final String UPDATE_PREFS = "updatePrefs";
Simon Hunt3678c2a2016-03-28 14:48:07 -0700111 private static final String SLASH = "/";
112
113 private static final int IDX_USER = 0;
114 private static final int IDX_KEY = 1;
Simon Hunte05cae42015-07-23 17:35:24 -0700115
Simon Hunt7379a3d2017-06-20 16:50:39 -0700116 private static final String LION_BASE = "/org/onosproject/ui/lion";
117
118 private static final String[] LION_TAGS = {
Simon Huntcffb2862017-06-23 12:02:43 -0700119 // framework component localization
120 "core.fw.Mast",
121 "core.fw.Nav",
122 "core.fw.QuickHelp",
123
124 // view component localization
125 "core.view.App",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700126 "core.view.Cluster",
Simon Hunt5989ddf2017-08-02 20:38:12 -0700127 "core.view.Topo",
ArturoF Velasco604ad0b2018-01-03 00:43:58 +0100128 "core.view.Flow",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700129
Simon Huntcffb2862017-06-23 12:02:43 -0700130 // TODO: More to come...
Simon Hunt7379a3d2017-06-20 16:50:39 -0700131 };
132
133
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800134 private final Logger log = LoggerFactory.getLogger(getClass());
135
Simon Huntd6d3ad32017-06-21 15:27:06 -0700136 // First thing to do is to set the locale (before creating core extension).
137 private final Locale runtimeLocale = LionUtils.setupRuntimeLocale();
138
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800139 // List of all extensions
140 private final List<UiExtension> extensions = Lists.newArrayList();
141
Laszlo Papp759f0d32018-03-05 13:24:30 +0000142 private final List<UiGlyph> glyphs = Lists.newArrayList();
143
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800144 // Map of views to extensions
145 private final Map<String, UiExtension> views = Maps.newHashMap();
146
147 // Core views & core extension
Thomas Vachuska3553b302015-03-07 14:49:43 -0800148 private final UiExtension core = createCoreExtension();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800149
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700150 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700151 protected MastershipService mastershipService;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800152
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700153 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska0af26912016-03-21 21:37:30 -0700154 protected StorageService storageService;
155
Ray Milkeyd84f89b2018-08-17 14:54:17 -0700156 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800157 private UiSharedTopologyModel sharedModel;
158
Thomas Vachuska0af26912016-03-21 21:37:30 -0700159 // User preferences
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700160 private ConsistentMap<String, ObjectNode> prefsConsistentMap;
161 private Map<String, ObjectNode> prefs;
162 private final MapEventListener<String, ObjectNode> prefsListener =
Thomas Vachuska0af26912016-03-21 21:37:30 -0700163 new InternalPrefsListener();
164
Simon Hunt1169c952017-06-05 11:20:11 -0700165 // Session tokens
166 private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
167 private Map<UiSessionToken, String> tokens;
168 private final SessionTokenGenerator tokenGen =
169 new SessionTokenGenerator();
170
Thomas Vachuska0af26912016-03-21 21:37:30 -0700171 private final ObjectMapper mapper = new ObjectMapper();
172
Madan Jampanibf8ee802016-05-04 14:07:36 -0700173 private final ExecutorService eventHandlingExecutor =
Simon Huntd5b96732016-07-08 13:22:27 -0700174 Executors.newSingleThreadExecutor(
175 Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Madan Jampanibf8ee802016-05-04 14:07:36 -0700176
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700177 private LionBundle navLion;
178
179
180 private String lionNavText(String id) {
181 return navLion.getValue("nav_item_" + id);
182 }
183
184 private UiView mkView(UiView.Category cat, String id, String iconId) {
185 return new UiView(cat, id, lionNavText(id), iconId);
186 }
187
Simon Huntc54cd1b2015-05-11 13:43:44 -0700188 private UiExtension createCoreExtension() {
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700189 List<LionBundle> lionBundles = generateBundles(LION_BASE, LION_TAGS);
190
191 navLion = lionBundles.stream()
192 .filter(f -> f.id().equals("core.fw.Nav")).findFirst().get();
193
Simon Hunt20e16792015-04-24 14:29:39 -0700194 List<UiView> coreViews = of(
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700195 mkView(PLATFORM, "app", "nav_apps"),
196 mkView(PLATFORM, "settings", "nav_settings"),
197 mkView(PLATFORM, "cluster", "nav_cluster"),
198 mkView(PLATFORM, "processor", "nav_processors"),
199 mkView(PLATFORM, "partition", "nav_partitions"),
200
201 mkView(NETWORK, "topo", "nav_topo"),
Thomas Vachuska175c33e2019-01-31 15:02:27 -0800202// mkView(NETWORK, "topo2", "nav_topo2"),
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700203 mkView(NETWORK, "device", "nav_devs"),
204
Simon Hunt20e16792015-04-24 14:29:39 -0700205 new UiViewHidden("flow"),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700206 new UiViewHidden("port"),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700207 new UiViewHidden("group"),
Jian Li1f544732015-12-30 23:36:37 -0800208 new UiViewHidden("meter"),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700209 new UiViewHidden("pipeconf"),
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700210
211 mkView(NETWORK, "link", "nav_links"),
212 mkView(NETWORK, "host", "nav_hosts"),
Thomas Vachuska52f2cd12018-11-08 21:20:04 -0800213 mkView(NETWORK, "intent", "nav_intents")
Simon Hunt20e16792015-04-24 14:29:39 -0700214 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700215
Simon Hunt4c7edd32015-03-11 10:42:53 -0700216 UiMessageHandlerFactory messageHandlerFactory =
217 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700218 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700219 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700220 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700221 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700222 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700223 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700224 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700225 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700226 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700227 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700228 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800229 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700230 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700231 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700232 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500233 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700234 new ProcessorViewMessageHandler(),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700235 new PartitionViewMessageHandler(),
236 new PipeconfViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700237 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700238
Simon Hunte05cae42015-07-23 17:35:24 -0700239 UiTopoOverlayFactory topoOverlayFactory =
240 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800241 new TrafficOverlay(),
242 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700243 );
244
Simon Hunt6cc86452017-04-27 17:46:22 -0700245 UiTopo2OverlayFactory topo2OverlayFactory =
246 () -> ImmutableList.of(
247 new Traffic2Overlay()
248 );
249
Steven Burrows3a9a6442016-05-05 15:31:16 +0100250 UiTopoMapFactory topoMapFactory =
251 () -> ImmutableList.of(
252 new UiTopoMap("australia", "Australia", "*australia", 1.0),
253 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
254 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
255 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700256 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100257 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700258 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100259 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700260 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100261 new UiTopoMap("japan", "Japan", "*japan", 0.8),
262 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
263 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
264 new UiTopoMap("africa", "Africa", "*africa", 0.7),
265 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
266 new UiTopoMap("asia", "Asia", "*asia", 0.7)
267 );
268
Simon Hunte05cae42015-07-23 17:35:24 -0700269 return new UiExtension.Builder(CL, coreViews)
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700270 .lionBundles(lionBundles)
Simon Hunte05cae42015-07-23 17:35:24 -0700271 .messageHandlerFactory(messageHandlerFactory)
272 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700273 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100274 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700275 .resourcePath(CORE)
Sean Condon98b6ddb2019-12-24 08:07:40 +0000276 .ui2()
Simon Hunte05cae42015-07-23 17:35:24 -0700277 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800278 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800279
Simon Hunt7379a3d2017-06-20 16:50:39 -0700280
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800281 @Activate
282 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700283 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700284 ObjectNode.class, ArrayNode.class,
285 JsonNodeFactory.class, LinkedHashMap.class,
286 TextNode.class, BooleanNode.class,
287 LongNode.class, DoubleNode.class, ShortNode.class,
288 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700289
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700290 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700291 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700292 .withSerializer(serializer)
293 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700294 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700295 prefsConsistentMap.addListener(prefsListener);
296 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700297
298 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
299 .withName(ONOS_SESSION_TOKENS)
300 .withSerializer(serializer)
301 .withRelaxedReadConsistency()
302 .build();
303 tokens = tokensConsistentMap.asJavaMap();
304
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800305 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700306
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800307 log.info("Started");
308 }
309
310 @Deactivate
311 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700312 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700313 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700314 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800315 unregister(core);
316 log.info("Stopped");
317 }
318
319 @Override
320 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900321 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800322 if (!extensions.contains(extension)) {
323 extensions.add(extension);
324 for (UiView view : extension.views()) {
325 views.put(view.id(), extension);
326 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700327 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800328 }
329 }
330
331 @Override
332 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900333 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800334 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700335 extension.views().stream()
336 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700337 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800338 }
339
340 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000341 public synchronized void register(UiGlyphFactory glyphFactory) {
342 checkPermission(GLYPH_WRITE);
343 boolean glyphAdded = false;
344 for (UiGlyph glyph : glyphFactory.glyphs()) {
345 if (!glyphs.contains(glyph)) {
346 glyphs.add(glyph);
347 glyphAdded = true;
348 }
349 }
350 if (glyphAdded) {
351 UiWebSocketServlet.sendToAll(GLYPH_ADDED, null);
352 }
353 }
354
355 @Override
356 public synchronized void unregister(UiGlyphFactory glyphFactory) {
357 checkPermission(GLYPH_WRITE);
358 boolean glyphRemoved = false;
359 for (UiGlyph glyph : glyphFactory.glyphs()) {
360 glyphs.remove(glyph);
361 glyphRemoved = true;
362 }
363 if (glyphRemoved) {
364 UiWebSocketServlet.sendToAll(GLYPH_REMOVED, null);
365 }
366 }
367
368 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800369 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900370 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800371 return ImmutableList.copyOf(extensions);
372 }
373
374 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000375 public synchronized List<UiGlyph> getGlyphs() {
376 checkPermission(GLYPH_READ);
377 return ImmutableList.copyOf(glyphs);
378 }
379
380 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800381 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900382 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800383 return views.get(viewId);
384 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700385
Thomas Vachuska0af26912016-03-21 21:37:30 -0700386 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700387 public synchronized LionBundle getNavLionBundle() {
388 return navLion;
389 }
390
391 @Override
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800392 public void refreshModel() {
393 sharedModel.reload();
394 }
395
396 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700397 public Set<String> getUserNames() {
398 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
399 prefs.keySet().forEach(k -> builder.add(userName(k)));
400 return builder.build();
401 }
402
403 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700404 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700405 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
406 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700407 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700408 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
409 return builder.build();
410 }
411
412 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700413 public ObjectNode getPreference(String username, String key) {
414 return prefs.get(key(username, key));
415 }
416
417 @Override
418 public void setPreference(String username, String key, ObjectNode value) {
Thomas Vachuska99b7bbe2018-02-01 15:29:46 -0800419 if (value != null) {
420 prefs.put(key(username, key), value);
421 } else {
422 prefs.remove(key(username, key));
423 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700424 }
425
Simon Huntc54cd1b2015-05-11 13:43:44 -0700426 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700427 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700428
429 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700430
431 @Override
432 public Set<String> getNames() {
433 return ImmutableSet.copyOf(sprites.keySet());
434 }
435
436 @Override
437 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700438 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700439 sprites.put(name, spriteData);
440 }
441
442 @Override
443 public JsonNode get(String name) {
444 return sprites.get(name);
445 }
446
Thomas Vachuska0af26912016-03-21 21:37:30 -0700447 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700448 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700449 }
450
Simon Hunt3678c2a2016-03-28 14:48:07 -0700451
Thomas Vachuska0af26912016-03-21 21:37:30 -0700452 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700453 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700454 }
455
456 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700457 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700458 }
459
Simon Hunt1169c952017-06-05 11:20:11 -0700460
461 // =====================================================================
462 // UiTokenService
463
464 @Override
465 public UiSessionToken issueToken(String username) {
466 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
467 tokens.put(token, username);
468 log.debug("UiSessionToken issued: {}", token);
469 return token;
470 }
471
472 @Override
473 public void revokeToken(UiSessionToken token) {
474 if (token != null) {
475 tokens.remove(token);
476 log.debug("UiSessionToken revoked: {}", token);
477 }
478 }
479
480 @Override
481 public boolean isTokenValid(UiSessionToken token) {
482 return token != null && tokens.containsKey(token);
483 }
484
485 private final class SessionTokenGenerator {
486 private final SecureRandom random = new SecureRandom();
487
488 /*
489 This works by choosing 130 bits from a cryptographically secure
490 random bit generator, and encoding them in base-32.
491
492 128 bits is considered to be cryptographically strong, but each
493 digit in a base 32 number can encode 5 bits, so 128 is rounded up
494 to the next multiple of 5.
495
496 This encoding is compact and efficient, with 5 random bits per
497 character. Compare this to a random UUID, which only has 3.4 bits
498 per character in standard layout, and only 122 random bits in total.
499
500 Note that SecureRandom objects are expensive to initialize, so
501 we'll want to keep it around and re-use it.
502 */
503
504 private String nextSessionId() {
505 return new BigInteger(130, random).toString(32);
506 }
507 }
508
Thomas Vachuska0af26912016-03-21 21:37:30 -0700509 // Auxiliary listener to preference map events.
510 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700511 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700512 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700513 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700514 eventHandlingExecutor.execute(() -> {
515 String userName = userName(event.key());
516 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
517 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
518 }
519 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700520 }
521
522 private ObjectNode jsonPrefs() {
523 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700524 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700525 return json;
526 }
527 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800528}