blob: e4034792eca5ebd8857482493da1579747452225 [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"),
202 mkView(NETWORK, "topo2", "nav_topo2"),
203 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)
276 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800277 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800278
Simon Hunt7379a3d2017-06-20 16:50:39 -0700279
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800280 @Activate
281 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700282 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700283 ObjectNode.class, ArrayNode.class,
284 JsonNodeFactory.class, LinkedHashMap.class,
285 TextNode.class, BooleanNode.class,
286 LongNode.class, DoubleNode.class, ShortNode.class,
287 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700288
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700289 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700290 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700291 .withSerializer(serializer)
292 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700293 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700294 prefsConsistentMap.addListener(prefsListener);
295 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700296
297 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
298 .withName(ONOS_SESSION_TOKENS)
299 .withSerializer(serializer)
300 .withRelaxedReadConsistency()
301 .build();
302 tokens = tokensConsistentMap.asJavaMap();
303
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800304 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700305
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800306 log.info("Started");
307 }
308
309 @Deactivate
310 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700311 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700312 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700313 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800314 unregister(core);
315 log.info("Stopped");
316 }
317
318 @Override
319 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900320 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800321 if (!extensions.contains(extension)) {
322 extensions.add(extension);
323 for (UiView view : extension.views()) {
324 views.put(view.id(), extension);
325 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700326 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800327 }
328 }
329
330 @Override
331 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900332 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800333 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700334 extension.views().stream()
335 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700336 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800337 }
338
339 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000340 public synchronized void register(UiGlyphFactory glyphFactory) {
341 checkPermission(GLYPH_WRITE);
342 boolean glyphAdded = false;
343 for (UiGlyph glyph : glyphFactory.glyphs()) {
344 if (!glyphs.contains(glyph)) {
345 glyphs.add(glyph);
346 glyphAdded = true;
347 }
348 }
349 if (glyphAdded) {
350 UiWebSocketServlet.sendToAll(GLYPH_ADDED, null);
351 }
352 }
353
354 @Override
355 public synchronized void unregister(UiGlyphFactory glyphFactory) {
356 checkPermission(GLYPH_WRITE);
357 boolean glyphRemoved = false;
358 for (UiGlyph glyph : glyphFactory.glyphs()) {
359 glyphs.remove(glyph);
360 glyphRemoved = true;
361 }
362 if (glyphRemoved) {
363 UiWebSocketServlet.sendToAll(GLYPH_REMOVED, null);
364 }
365 }
366
367 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800368 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900369 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800370 return ImmutableList.copyOf(extensions);
371 }
372
373 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000374 public synchronized List<UiGlyph> getGlyphs() {
375 checkPermission(GLYPH_READ);
376 return ImmutableList.copyOf(glyphs);
377 }
378
379 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800380 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900381 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800382 return views.get(viewId);
383 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700384
Thomas Vachuska0af26912016-03-21 21:37:30 -0700385 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700386 public synchronized LionBundle getNavLionBundle() {
387 return navLion;
388 }
389
390 @Override
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800391 public void refreshModel() {
392 sharedModel.reload();
393 }
394
395 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700396 public Set<String> getUserNames() {
397 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
398 prefs.keySet().forEach(k -> builder.add(userName(k)));
399 return builder.build();
400 }
401
402 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700403 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700404 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
405 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700406 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700407 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
408 return builder.build();
409 }
410
411 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700412 public ObjectNode getPreference(String username, String key) {
413 return prefs.get(key(username, key));
414 }
415
416 @Override
417 public void setPreference(String username, String key, ObjectNode value) {
Thomas Vachuska99b7bbe2018-02-01 15:29:46 -0800418 if (value != null) {
419 prefs.put(key(username, key), value);
420 } else {
421 prefs.remove(key(username, key));
422 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700423 }
424
Simon Huntc54cd1b2015-05-11 13:43:44 -0700425 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700426 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700427
428 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700429
430 @Override
431 public Set<String> getNames() {
432 return ImmutableSet.copyOf(sprites.keySet());
433 }
434
435 @Override
436 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700437 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700438 sprites.put(name, spriteData);
439 }
440
441 @Override
442 public JsonNode get(String name) {
443 return sprites.get(name);
444 }
445
Thomas Vachuska0af26912016-03-21 21:37:30 -0700446 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700447 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700448 }
449
Simon Hunt3678c2a2016-03-28 14:48:07 -0700450
Thomas Vachuska0af26912016-03-21 21:37:30 -0700451 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700452 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700453 }
454
455 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700456 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700457 }
458
Simon Hunt1169c952017-06-05 11:20:11 -0700459
460 // =====================================================================
461 // UiTokenService
462
463 @Override
464 public UiSessionToken issueToken(String username) {
465 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
466 tokens.put(token, username);
467 log.debug("UiSessionToken issued: {}", token);
468 return token;
469 }
470
471 @Override
472 public void revokeToken(UiSessionToken token) {
473 if (token != null) {
474 tokens.remove(token);
475 log.debug("UiSessionToken revoked: {}", token);
476 }
477 }
478
479 @Override
480 public boolean isTokenValid(UiSessionToken token) {
481 return token != null && tokens.containsKey(token);
482 }
483
484 private final class SessionTokenGenerator {
485 private final SecureRandom random = new SecureRandom();
486
487 /*
488 This works by choosing 130 bits from a cryptographically secure
489 random bit generator, and encoding them in base-32.
490
491 128 bits is considered to be cryptographically strong, but each
492 digit in a base 32 number can encode 5 bits, so 128 is rounded up
493 to the next multiple of 5.
494
495 This encoding is compact and efficient, with 5 random bits per
496 character. Compare this to a random UUID, which only has 3.4 bits
497 per character in standard layout, and only 122 random bits in total.
498
499 Note that SecureRandom objects are expensive to initialize, so
500 we'll want to keep it around and re-use it.
501 */
502
503 private String nextSessionId() {
504 return new BigInteger(130, random).toString(32);
505 }
506 }
507
Thomas Vachuska0af26912016-03-21 21:37:30 -0700508 // Auxiliary listener to preference map events.
509 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700510 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700511 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700512 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700513 eventHandlingExecutor.execute(() -> {
514 String userName = userName(event.key());
515 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
516 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
517 }
518 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700519 }
520
521 private ObjectNode jsonPrefs() {
522 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700523 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700524 return json;
525 }
526 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800527}