blob: c776fb35468f101797c437ec863d50a248ca2b2a [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"),
213 mkView(NETWORK, "intent", "nav_intents"),
214 mkView(NETWORK, "tunnel", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700215 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700216
Simon Hunt4c7edd32015-03-11 10:42:53 -0700217 UiMessageHandlerFactory messageHandlerFactory =
218 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700219 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700220 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700221 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700222 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700223 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700224 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700225 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700226 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700227 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700228 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700229 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800230 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700231 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700232 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700233 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500234 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700235 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800236 new TunnelViewMessageHandler(),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700237 new PartitionViewMessageHandler(),
238 new PipeconfViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700239 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700240
Simon Hunte05cae42015-07-23 17:35:24 -0700241 UiTopoOverlayFactory topoOverlayFactory =
242 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800243 new TrafficOverlay(),
244 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700245 );
246
Simon Hunt6cc86452017-04-27 17:46:22 -0700247 UiTopo2OverlayFactory topo2OverlayFactory =
248 () -> ImmutableList.of(
249 new Traffic2Overlay()
250 );
251
Steven Burrows3a9a6442016-05-05 15:31:16 +0100252 UiTopoMapFactory topoMapFactory =
253 () -> ImmutableList.of(
254 new UiTopoMap("australia", "Australia", "*australia", 1.0),
255 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
256 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
257 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700258 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100259 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700260 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100261 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700262 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100263 new UiTopoMap("japan", "Japan", "*japan", 0.8),
264 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
265 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
266 new UiTopoMap("africa", "Africa", "*africa", 0.7),
267 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
268 new UiTopoMap("asia", "Asia", "*asia", 0.7)
269 );
270
Simon Hunte05cae42015-07-23 17:35:24 -0700271 return new UiExtension.Builder(CL, coreViews)
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700272 .lionBundles(lionBundles)
Simon Hunte05cae42015-07-23 17:35:24 -0700273 .messageHandlerFactory(messageHandlerFactory)
274 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700275 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100276 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700277 .resourcePath(CORE)
278 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800279 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800280
Simon Hunt7379a3d2017-06-20 16:50:39 -0700281
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800282 @Activate
283 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700284 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700285 ObjectNode.class, ArrayNode.class,
286 JsonNodeFactory.class, LinkedHashMap.class,
287 TextNode.class, BooleanNode.class,
288 LongNode.class, DoubleNode.class, ShortNode.class,
289 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700290
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700291 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700292 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700293 .withSerializer(serializer)
294 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700295 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700296 prefsConsistentMap.addListener(prefsListener);
297 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700298
299 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
300 .withName(ONOS_SESSION_TOKENS)
301 .withSerializer(serializer)
302 .withRelaxedReadConsistency()
303 .build();
304 tokens = tokensConsistentMap.asJavaMap();
305
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800306 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700307
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800308 log.info("Started");
309 }
310
311 @Deactivate
312 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700313 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700314 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700315 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800316 unregister(core);
317 log.info("Stopped");
318 }
319
320 @Override
321 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900322 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800323 if (!extensions.contains(extension)) {
324 extensions.add(extension);
325 for (UiView view : extension.views()) {
326 views.put(view.id(), extension);
327 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700328 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800329 }
330 }
331
332 @Override
333 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900334 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800335 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700336 extension.views().stream()
337 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700338 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800339 }
340
341 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000342 public synchronized void register(UiGlyphFactory glyphFactory) {
343 checkPermission(GLYPH_WRITE);
344 boolean glyphAdded = false;
345 for (UiGlyph glyph : glyphFactory.glyphs()) {
346 if (!glyphs.contains(glyph)) {
347 glyphs.add(glyph);
348 glyphAdded = true;
349 }
350 }
351 if (glyphAdded) {
352 UiWebSocketServlet.sendToAll(GLYPH_ADDED, null);
353 }
354 }
355
356 @Override
357 public synchronized void unregister(UiGlyphFactory glyphFactory) {
358 checkPermission(GLYPH_WRITE);
359 boolean glyphRemoved = false;
360 for (UiGlyph glyph : glyphFactory.glyphs()) {
361 glyphs.remove(glyph);
362 glyphRemoved = true;
363 }
364 if (glyphRemoved) {
365 UiWebSocketServlet.sendToAll(GLYPH_REMOVED, null);
366 }
367 }
368
369 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800370 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900371 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800372 return ImmutableList.copyOf(extensions);
373 }
374
375 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000376 public synchronized List<UiGlyph> getGlyphs() {
377 checkPermission(GLYPH_READ);
378 return ImmutableList.copyOf(glyphs);
379 }
380
381 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800382 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900383 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800384 return views.get(viewId);
385 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700386
Thomas Vachuska0af26912016-03-21 21:37:30 -0700387 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700388 public synchronized LionBundle getNavLionBundle() {
389 return navLion;
390 }
391
392 @Override
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800393 public void refreshModel() {
394 sharedModel.reload();
395 }
396
397 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700398 public Set<String> getUserNames() {
399 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
400 prefs.keySet().forEach(k -> builder.add(userName(k)));
401 return builder.build();
402 }
403
404 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700405 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700406 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
407 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700408 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700409 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
410 return builder.build();
411 }
412
413 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700414 public ObjectNode getPreference(String username, String key) {
415 return prefs.get(key(username, key));
416 }
417
418 @Override
419 public void setPreference(String username, String key, ObjectNode value) {
Thomas Vachuska99b7bbe2018-02-01 15:29:46 -0800420 if (value != null) {
421 prefs.put(key(username, key), value);
422 } else {
423 prefs.remove(key(username, key));
424 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700425 }
426
Simon Huntc54cd1b2015-05-11 13:43:44 -0700427 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700428 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700429
430 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700431
432 @Override
433 public Set<String> getNames() {
434 return ImmutableSet.copyOf(sprites.keySet());
435 }
436
437 @Override
438 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700439 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700440 sprites.put(name, spriteData);
441 }
442
443 @Override
444 public JsonNode get(String name) {
445 return sprites.get(name);
446 }
447
Thomas Vachuska0af26912016-03-21 21:37:30 -0700448 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700449 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700450 }
451
Simon Hunt3678c2a2016-03-28 14:48:07 -0700452
Thomas Vachuska0af26912016-03-21 21:37:30 -0700453 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700454 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700455 }
456
457 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700458 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700459 }
460
Simon Hunt1169c952017-06-05 11:20:11 -0700461
462 // =====================================================================
463 // UiTokenService
464
465 @Override
466 public UiSessionToken issueToken(String username) {
467 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
468 tokens.put(token, username);
469 log.debug("UiSessionToken issued: {}", token);
470 return token;
471 }
472
473 @Override
474 public void revokeToken(UiSessionToken token) {
475 if (token != null) {
476 tokens.remove(token);
477 log.debug("UiSessionToken revoked: {}", token);
478 }
479 }
480
481 @Override
482 public boolean isTokenValid(UiSessionToken token) {
483 return token != null && tokens.containsKey(token);
484 }
485
486 private final class SessionTokenGenerator {
487 private final SecureRandom random = new SecureRandom();
488
489 /*
490 This works by choosing 130 bits from a cryptographically secure
491 random bit generator, and encoding them in base-32.
492
493 128 bits is considered to be cryptographically strong, but each
494 digit in a base 32 number can encode 5 bits, so 128 is rounded up
495 to the next multiple of 5.
496
497 This encoding is compact and efficient, with 5 random bits per
498 character. Compare this to a random UUID, which only has 3.4 bits
499 per character in standard layout, and only 122 random bits in total.
500
501 Note that SecureRandom objects are expensive to initialize, so
502 we'll want to keep it around and re-use it.
503 */
504
505 private String nextSessionId() {
506 return new BigInteger(130, random).toString(32);
507 }
508 }
509
Thomas Vachuska0af26912016-03-21 21:37:30 -0700510 // Auxiliary listener to preference map events.
511 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700512 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700513 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700514 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700515 eventHandlingExecutor.execute(() -> {
516 String userName = userName(event.key());
517 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
518 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
519 }
520 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700521 }
522
523 private ObjectNode jsonPrefs() {
524 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700525 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700526 return json;
527 }
528 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800529}