blob: bb24c89f4fbf8dede54b41c5077f2d321ded3512 [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;
Laszlo Papp759f0d32018-03-05 13:24:30 +000051import org.onosproject.ui.UiGlyph;
52import org.onosproject.ui.UiGlyphFactory;
Thomas Vachuska3553b302015-03-07 14:49:43 -080053import org.onosproject.ui.UiMessageHandlerFactory;
Thomas Vachuska0af26912016-03-21 21:37:30 -070054import org.onosproject.ui.UiPreferencesService;
Simon Hunt1169c952017-06-05 11:20:11 -070055import org.onosproject.ui.UiSessionToken;
56import org.onosproject.ui.UiTokenService;
Simon Hunt6cc86452017-04-27 17:46:22 -070057import org.onosproject.ui.UiTopo2OverlayFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070058import org.onosproject.ui.UiTopoMap;
Steven Burrows3a9a6442016-05-05 15:31:16 +010059import org.onosproject.ui.UiTopoMapFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070060import org.onosproject.ui.UiTopoOverlayFactory;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080061import org.onosproject.ui.UiView;
Simon Hunt1002cd82015-04-23 14:44:03 -070062import org.onosproject.ui.UiViewHidden;
Simon Hunte6f64612017-04-28 00:01:48 -070063import org.onosproject.ui.impl.topo.Topo2TrafficMessageHandler;
Simon Huntd5b96732016-07-08 13:22:27 -070064import org.onosproject.ui.impl.topo.Topo2ViewMessageHandler;
Simon Hunt6cc86452017-04-27 17:46:22 -070065import org.onosproject.ui.impl.topo.Traffic2Overlay;
Thomas Vachuska1b1355d2018-02-06 16:53:58 -080066import org.onosproject.ui.impl.topo.model.UiSharedTopologyModel;
Simon Hunt23f9c7b2017-07-10 20:00:30 -070067import org.onosproject.ui.lion.LionBundle;
Simon Huntb8042032017-06-13 15:03:23 -070068import org.onosproject.ui.lion.LionUtils;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080069import org.slf4j.Logger;
70import org.slf4j.LoggerFactory;
71
Simon Hunt1169c952017-06-05 11:20:11 -070072import java.math.BigInteger;
73import java.security.SecureRandom;
Thomas Vachuska0af26912016-03-21 21:37:30 -070074import java.util.LinkedHashMap;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080075import java.util.List;
Simon Huntd6d3ad32017-06-21 15:27:06 -070076import java.util.Locale;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080077import java.util.Map;
Thomas Vachuska9ed335b2015-04-14 12:07:47 -070078import java.util.Set;
Madan Jampanibf8ee802016-05-04 14:07:36 -070079import java.util.concurrent.ExecutorService;
80import java.util.concurrent.Executors;
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080081
82import static com.google.common.collect.ImmutableList.of;
83import static java.util.stream.Collectors.toSet;
Heedo Kang4a47a302016-02-29 17:40:23 +090084import static org.onosproject.security.AppGuard.checkPermission;
85import static org.onosproject.security.AppPermission.Type.UI_READ;
86import static org.onosproject.security.AppPermission.Type.UI_WRITE;
Laszlo Papp759f0d32018-03-05 13:24:30 +000087import static org.onosproject.security.AppPermission.Type.GLYPH_READ;
88import static org.onosproject.security.AppPermission.Type.GLYPH_WRITE;
Thomas Vachuska0af26912016-03-21 21:37:30 -070089import static org.onosproject.ui.UiView.Category.NETWORK;
90import static org.onosproject.ui.UiView.Category.PLATFORM;
Simon Huntd8754652017-06-21 11:45:22 -070091import static org.onosproject.ui.impl.lion.BundleStitcher.generateBundles;
Heedo Kang4a47a302016-02-29 17:40:23 +090092
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -080093/**
94 * Manages the user interface extensions.
95 */
96@Component(immediate = true)
97@Service
Simon Hunt3678c2a2016-03-28 14:48:07 -070098public class UiExtensionManager
Simon Hunt1169c952017-06-05 11:20:11 -070099 implements UiExtensionService, UiPreferencesService, SpriteService,
100 UiTokenService {
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800101
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700102 private static final ClassLoader CL = UiExtensionManager.class.getClassLoader();
Simon Hunt3678c2a2016-03-28 14:48:07 -0700103
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700104 private static final String ONOS_USER_PREFERENCES = "onos-ui-user-preferences";
Simon Hunt1169c952017-06-05 11:20:11 -0700105 private static final String ONOS_SESSION_TOKENS = "onos-ui-session-tokens";
Simon Hunte05cae42015-07-23 17:35:24 -0700106 private static final String CORE = "core";
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700107 private static final String GUI_ADDED = "guiAdded";
108 private static final String GUI_REMOVED = "guiRemoved";
Laszlo Papp759f0d32018-03-05 13:24:30 +0000109 private static final String GLYPH_ADDED = "glyphAdded";
110 private static final String GLYPH_REMOVED = "glyphRemoved";
Thomas Vachuska0af26912016-03-21 21:37:30 -0700111 private static final String UPDATE_PREFS = "updatePrefs";
Simon Hunt3678c2a2016-03-28 14:48:07 -0700112 private static final String SLASH = "/";
113
114 private static final int IDX_USER = 0;
115 private static final int IDX_KEY = 1;
Simon Hunte05cae42015-07-23 17:35:24 -0700116
Simon Hunt7379a3d2017-06-20 16:50:39 -0700117 private static final String LION_BASE = "/org/onosproject/ui/lion";
118
119 private static final String[] LION_TAGS = {
Simon Huntcffb2862017-06-23 12:02:43 -0700120 // framework component localization
121 "core.fw.Mast",
122 "core.fw.Nav",
123 "core.fw.QuickHelp",
124
125 // view component localization
126 "core.view.App",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700127 "core.view.Cluster",
Simon Hunt5989ddf2017-08-02 20:38:12 -0700128 "core.view.Topo",
ArturoF Velasco604ad0b2018-01-03 00:43:58 +0100129 "core.view.Flow",
Simon Hunt7379a3d2017-06-20 16:50:39 -0700130
Simon Huntcffb2862017-06-23 12:02:43 -0700131 // TODO: More to come...
Simon Hunt7379a3d2017-06-20 16:50:39 -0700132 };
133
134
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800135 private final Logger log = LoggerFactory.getLogger(getClass());
136
Simon Huntd6d3ad32017-06-21 15:27:06 -0700137 // First thing to do is to set the locale (before creating core extension).
138 private final Locale runtimeLocale = LionUtils.setupRuntimeLocale();
139
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800140 // List of all extensions
141 private final List<UiExtension> extensions = Lists.newArrayList();
142
Laszlo Papp759f0d32018-03-05 13:24:30 +0000143 private final List<UiGlyph> glyphs = Lists.newArrayList();
144
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800145 // Map of views to extensions
146 private final Map<String, UiExtension> views = Maps.newHashMap();
147
148 // Core views & core extension
Thomas Vachuska3553b302015-03-07 14:49:43 -0800149 private final UiExtension core = createCoreExtension();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800150
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700151 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
152 protected MastershipService mastershipService;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800153
Thomas Vachuska0af26912016-03-21 21:37:30 -0700154 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
155 protected StorageService storageService;
156
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800157 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
158 private UiSharedTopologyModel sharedModel;
159
Thomas Vachuska0af26912016-03-21 21:37:30 -0700160 // User preferences
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700161 private ConsistentMap<String, ObjectNode> prefsConsistentMap;
162 private Map<String, ObjectNode> prefs;
163 private final MapEventListener<String, ObjectNode> prefsListener =
Thomas Vachuska0af26912016-03-21 21:37:30 -0700164 new InternalPrefsListener();
165
Simon Hunt1169c952017-06-05 11:20:11 -0700166 // Session tokens
167 private ConsistentMap<UiSessionToken, String> tokensConsistentMap;
168 private Map<UiSessionToken, String> tokens;
169 private final SessionTokenGenerator tokenGen =
170 new SessionTokenGenerator();
171
Thomas Vachuska0af26912016-03-21 21:37:30 -0700172 private final ObjectMapper mapper = new ObjectMapper();
173
Madan Jampanibf8ee802016-05-04 14:07:36 -0700174 private final ExecutorService eventHandlingExecutor =
Simon Huntd5b96732016-07-08 13:22:27 -0700175 Executors.newSingleThreadExecutor(
176 Tools.groupedThreads("onos/ui-ext-manager", "event-handler", log));
Madan Jampanibf8ee802016-05-04 14:07:36 -0700177
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700178 private LionBundle navLion;
179
180
181 private String lionNavText(String id) {
182 return navLion.getValue("nav_item_" + id);
183 }
184
185 private UiView mkView(UiView.Category cat, String id, String iconId) {
186 return new UiView(cat, id, lionNavText(id), iconId);
187 }
188
Simon Huntc54cd1b2015-05-11 13:43:44 -0700189 private UiExtension createCoreExtension() {
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700190 List<LionBundle> lionBundles = generateBundles(LION_BASE, LION_TAGS);
191
192 navLion = lionBundles.stream()
193 .filter(f -> f.id().equals("core.fw.Nav")).findFirst().get();
194
Simon Hunt20e16792015-04-24 14:29:39 -0700195 List<UiView> coreViews = of(
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700196 mkView(PLATFORM, "app", "nav_apps"),
197 mkView(PLATFORM, "settings", "nav_settings"),
198 mkView(PLATFORM, "cluster", "nav_cluster"),
199 mkView(PLATFORM, "processor", "nav_processors"),
200 mkView(PLATFORM, "partition", "nav_partitions"),
201
202 mkView(NETWORK, "topo", "nav_topo"),
203 mkView(NETWORK, "topo2", "nav_topo2"),
204 mkView(NETWORK, "device", "nav_devs"),
205
Simon Hunt20e16792015-04-24 14:29:39 -0700206 new UiViewHidden("flow"),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700207 new UiViewHidden("port"),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700208 new UiViewHidden("group"),
Jian Li1f544732015-12-30 23:36:37 -0800209 new UiViewHidden("meter"),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700210 new UiViewHidden("pipeconf"),
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700211
212 mkView(NETWORK, "link", "nav_links"),
213 mkView(NETWORK, "host", "nav_hosts"),
214 mkView(NETWORK, "intent", "nav_intents"),
215 mkView(NETWORK, "tunnel", "nav_tunnels")
Simon Hunt20e16792015-04-24 14:29:39 -0700216 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700217
Simon Hunt4c7edd32015-03-11 10:42:53 -0700218 UiMessageHandlerFactory messageHandlerFactory =
219 () -> ImmutableList.of(
Thomas Vachuska0af26912016-03-21 21:37:30 -0700220 new UserPreferencesMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700221 new TopologyViewMessageHandler(),
Simon Huntd5b96732016-07-08 13:22:27 -0700222 new Topo2ViewMessageHandler(),
Simon Hunte6f64612017-04-28 00:01:48 -0700223 new Topo2TrafficMessageHandler(),
Thomas Vachuska26be4f32016-03-31 01:10:27 -0700224 new MapSelectorMessageHandler(),
Thomas Vachuskae586b792015-03-26 13:59:38 -0700225 new DeviceViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700226 new LinkViewMessageHandler(),
Thomas Vachuska0fa583c2015-03-30 23:07:41 -0700227 new HostViewMessageHandler(),
Bri Prebilic Colecdc188d2015-04-24 16:40:11 -0700228 new FlowViewMessageHandler(),
Bri Prebilic Coleac829e42015-05-05 13:42:06 -0700229 new PortViewMessageHandler(),
Bri Prebilic Coleff3dc672015-05-06 12:59:38 -0700230 new GroupViewMessageHandler(),
Jian Li1f544732015-12-30 23:36:37 -0800231 new MeterViewMessageHandler(),
Bri Prebilic Cole384e8dc2015-04-13 15:51:14 -0700232 new IntentViewMessageHandler(),
Thomas Vachuska583bc632015-04-14 10:10:57 -0700233 new ApplicationViewMessageHandler(),
Thomas Vachuskaaa8b0eb2015-05-22 09:54:15 -0700234 new SettingsViewMessageHandler(),
chengfanb466a7e2015-08-21 09:59:29 -0500235 new ClusterViewMessageHandler(),
Thomas Vachuska3ece3732015-09-22 23:58:50 -0700236 new ProcessorViewMessageHandler(),
chengfanc5a99dc2017-01-08 19:28:29 +0800237 new TunnelViewMessageHandler(),
Yi Tsenga87b40c2017-09-10 00:59:03 -0700238 new PartitionViewMessageHandler(),
239 new PipeconfViewMessageHandler()
Simon Hunt4c7edd32015-03-11 10:42:53 -0700240 );
Simon Hunt1002cd82015-04-23 14:44:03 -0700241
Simon Hunte05cae42015-07-23 17:35:24 -0700242 UiTopoOverlayFactory topoOverlayFactory =
243 () -> ImmutableList.of(
Andrea Campanella732ea832017-02-06 09:25:59 -0800244 new TrafficOverlay(),
245 new ProtectedIntentOverlay()
Simon Hunte05cae42015-07-23 17:35:24 -0700246 );
247
Simon Hunt6cc86452017-04-27 17:46:22 -0700248 UiTopo2OverlayFactory topo2OverlayFactory =
249 () -> ImmutableList.of(
250 new Traffic2Overlay()
251 );
252
Steven Burrows3a9a6442016-05-05 15:31:16 +0100253 UiTopoMapFactory topoMapFactory =
254 () -> ImmutableList.of(
255 new UiTopoMap("australia", "Australia", "*australia", 1.0),
256 new UiTopoMap("americas", "North, Central and South America", "*americas", 0.7),
257 new UiTopoMap("n_america", "North America", "*n_america", 0.9),
258 new UiTopoMap("s_america", "South America", "*s_america", 0.9),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700259 new UiTopoMap("usa", "United States", "*continental_us", 1.3),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100260 new UiTopoMap("bayareaGEO", "Bay Area, California", "*bayarea", 1.0),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700261 new UiTopoMap("europe", "Europe", "*europe", 10.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100262 new UiTopoMap("italy", "Italy", "*italy", 0.8),
Thomas Vachuskacc0b7d62016-07-12 14:03:11 -0700263 new UiTopoMap("uk", "United Kingdom and Ireland", "*uk", 2.0),
Steven Burrows3a9a6442016-05-05 15:31:16 +0100264 new UiTopoMap("japan", "Japan", "*japan", 0.8),
265 new UiTopoMap("s_korea", "South Korea", "*s_korea", 0.75),
266 new UiTopoMap("taiwan", "Taiwan", "*taiwan", 0.7),
267 new UiTopoMap("africa", "Africa", "*africa", 0.7),
268 new UiTopoMap("oceania", "Oceania", "*oceania", 0.7),
269 new UiTopoMap("asia", "Asia", "*asia", 0.7)
270 );
271
Simon Hunte05cae42015-07-23 17:35:24 -0700272 return new UiExtension.Builder(CL, coreViews)
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700273 .lionBundles(lionBundles)
Simon Hunte05cae42015-07-23 17:35:24 -0700274 .messageHandlerFactory(messageHandlerFactory)
275 .topoOverlayFactory(topoOverlayFactory)
Simon Hunt6cc86452017-04-27 17:46:22 -0700276 .topo2OverlayFactory(topo2OverlayFactory)
Steven Burrows3a9a6442016-05-05 15:31:16 +0100277 .topoMapFactory(topoMapFactory)
Simon Hunte05cae42015-07-23 17:35:24 -0700278 .resourcePath(CORE)
279 .build();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800280 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800281
Simon Hunt7379a3d2017-06-20 16:50:39 -0700282
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800283 @Activate
284 public void activate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700285 Serializer serializer = Serializer.using(KryoNamespaces.API,
Simon Huntd6d3ad32017-06-21 15:27:06 -0700286 ObjectNode.class, ArrayNode.class,
287 JsonNodeFactory.class, LinkedHashMap.class,
288 TextNode.class, BooleanNode.class,
289 LongNode.class, DoubleNode.class, ShortNode.class,
290 IntNode.class, NullNode.class, UiSessionToken.class);
Thomas Vachuska0af26912016-03-21 21:37:30 -0700291
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700292 prefsConsistentMap = storageService.<String, ObjectNode>consistentMapBuilder()
Simon Hunt3678c2a2016-03-28 14:48:07 -0700293 .withName(ONOS_USER_PREFERENCES)
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700294 .withSerializer(serializer)
295 .withRelaxedReadConsistency()
Thomas Vachuska0af26912016-03-21 21:37:30 -0700296 .build();
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700297 prefsConsistentMap.addListener(prefsListener);
298 prefs = prefsConsistentMap.asJavaMap();
Simon Hunt1169c952017-06-05 11:20:11 -0700299
300 tokensConsistentMap = storageService.<UiSessionToken, String>consistentMapBuilder()
301 .withName(ONOS_SESSION_TOKENS)
302 .withSerializer(serializer)
303 .withRelaxedReadConsistency()
304 .build();
305 tokens = tokensConsistentMap.asJavaMap();
306
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800307 register(core);
Simon Huntb8042032017-06-13 15:03:23 -0700308
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800309 log.info("Started");
310 }
311
312 @Deactivate
313 public void deactivate() {
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700314 prefsConsistentMap.removeListener(prefsListener);
Madan Jampanibf8ee802016-05-04 14:07:36 -0700315 eventHandlingExecutor.shutdown();
Thomas Vachuska51f540f2015-05-27 17:26:57 -0700316 UiWebSocketServlet.closeAll();
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800317 unregister(core);
318 log.info("Stopped");
319 }
320
321 @Override
322 public synchronized void register(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900323 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800324 if (!extensions.contains(extension)) {
325 extensions.add(extension);
326 for (UiView view : extension.views()) {
327 views.put(view.id(), extension);
328 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700329 UiWebSocketServlet.sendToAll(GUI_ADDED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800330 }
331 }
332
333 @Override
334 public synchronized void unregister(UiExtension extension) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900335 checkPermission(UI_WRITE);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800336 extensions.remove(extension);
Simon Huntd6d3ad32017-06-21 15:27:06 -0700337 extension.views().stream()
338 .map(UiView::id).collect(toSet()).forEach(views::remove);
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700339 UiWebSocketServlet.sendToAll(GUI_REMOVED, null);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800340 }
341
342 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000343 public synchronized void register(UiGlyphFactory glyphFactory) {
344 checkPermission(GLYPH_WRITE);
345 boolean glyphAdded = false;
346 for (UiGlyph glyph : glyphFactory.glyphs()) {
347 if (!glyphs.contains(glyph)) {
348 glyphs.add(glyph);
349 glyphAdded = true;
350 }
351 }
352 if (glyphAdded) {
353 UiWebSocketServlet.sendToAll(GLYPH_ADDED, null);
354 }
355 }
356
357 @Override
358 public synchronized void unregister(UiGlyphFactory glyphFactory) {
359 checkPermission(GLYPH_WRITE);
360 boolean glyphRemoved = false;
361 for (UiGlyph glyph : glyphFactory.glyphs()) {
362 glyphs.remove(glyph);
363 glyphRemoved = true;
364 }
365 if (glyphRemoved) {
366 UiWebSocketServlet.sendToAll(GLYPH_REMOVED, null);
367 }
368 }
369
370 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800371 public synchronized List<UiExtension> getExtensions() {
Heedo Kang4a47a302016-02-29 17:40:23 +0900372 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800373 return ImmutableList.copyOf(extensions);
374 }
375
376 @Override
Laszlo Papp759f0d32018-03-05 13:24:30 +0000377 public synchronized List<UiGlyph> getGlyphs() {
378 checkPermission(GLYPH_READ);
379 return ImmutableList.copyOf(glyphs);
380 }
381
382 @Override
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800383 public synchronized UiExtension getViewExtension(String viewId) {
Heedo Kang4a47a302016-02-29 17:40:23 +0900384 checkPermission(UI_READ);
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800385 return views.get(viewId);
386 }
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700387
Thomas Vachuska0af26912016-03-21 21:37:30 -0700388 @Override
Simon Hunt23f9c7b2017-07-10 20:00:30 -0700389 public synchronized LionBundle getNavLionBundle() {
390 return navLion;
391 }
392
393 @Override
Thomas Vachuska1b1355d2018-02-06 16:53:58 -0800394 public void refreshModel() {
395 sharedModel.reload();
396 }
397
398 @Override
Thomas Vachuska0af26912016-03-21 21:37:30 -0700399 public Set<String> getUserNames() {
400 ImmutableSet.Builder<String> builder = ImmutableSet.builder();
401 prefs.keySet().forEach(k -> builder.add(userName(k)));
402 return builder.build();
403 }
404
405 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700406 public Map<String, ObjectNode> getPreferences(String username) {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700407 ImmutableMap.Builder<String, ObjectNode> builder = ImmutableMap.builder();
408 prefs.entrySet().stream()
Simon Huntcfef6f92017-07-25 16:58:42 -0700409 .filter(e -> e.getKey().startsWith(username + SLASH))
Thomas Vachuska0af26912016-03-21 21:37:30 -0700410 .forEach(e -> builder.put(keyName(e.getKey()), e.getValue()));
411 return builder.build();
412 }
413
414 @Override
Simon Huntcfef6f92017-07-25 16:58:42 -0700415 public ObjectNode getPreference(String username, String key) {
416 return prefs.get(key(username, key));
417 }
418
419 @Override
420 public void setPreference(String username, String key, ObjectNode value) {
Thomas Vachuska99b7bbe2018-02-01 15:29:46 -0800421 if (value != null) {
422 prefs.put(key(username, key), value);
423 } else {
424 prefs.remove(key(username, key));
425 }
Thomas Vachuska0af26912016-03-21 21:37:30 -0700426 }
427
Simon Huntc54cd1b2015-05-11 13:43:44 -0700428 // =====================================================================
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700429 // Provisional tracking of sprite definitions
Simon Huntc54cd1b2015-05-11 13:43:44 -0700430
431 private final Map<String, JsonNode> sprites = Maps.newHashMap();
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700432
433 @Override
434 public Set<String> getNames() {
435 return ImmutableSet.copyOf(sprites.keySet());
436 }
437
438 @Override
439 public void put(String name, JsonNode spriteData) {
Simon Huntfd8c7d72015-04-14 17:53:37 -0700440 log.info("Registered sprite definition [{}]", name);
Thomas Vachuska9ed335b2015-04-14 12:07:47 -0700441 sprites.put(name, spriteData);
442 }
443
444 @Override
445 public JsonNode get(String name) {
446 return sprites.get(name);
447 }
448
Thomas Vachuska0af26912016-03-21 21:37:30 -0700449 private String key(String userName, String keyName) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700450 return userName + SLASH + keyName;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700451 }
452
Simon Hunt3678c2a2016-03-28 14:48:07 -0700453
Thomas Vachuska0af26912016-03-21 21:37:30 -0700454 private String userName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700455 return key.split(SLASH)[IDX_USER];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700456 }
457
458 private String keyName(String key) {
Simon Hunt3678c2a2016-03-28 14:48:07 -0700459 return key.split(SLASH)[IDX_KEY];
Thomas Vachuska0af26912016-03-21 21:37:30 -0700460 }
461
Simon Hunt1169c952017-06-05 11:20:11 -0700462
463 // =====================================================================
464 // UiTokenService
465
466 @Override
467 public UiSessionToken issueToken(String username) {
468 UiSessionToken token = new UiSessionToken(tokenGen.nextSessionId());
469 tokens.put(token, username);
470 log.debug("UiSessionToken issued: {}", token);
471 return token;
472 }
473
474 @Override
475 public void revokeToken(UiSessionToken token) {
476 if (token != null) {
477 tokens.remove(token);
478 log.debug("UiSessionToken revoked: {}", token);
479 }
480 }
481
482 @Override
483 public boolean isTokenValid(UiSessionToken token) {
484 return token != null && tokens.containsKey(token);
485 }
486
487 private final class SessionTokenGenerator {
488 private final SecureRandom random = new SecureRandom();
489
490 /*
491 This works by choosing 130 bits from a cryptographically secure
492 random bit generator, and encoding them in base-32.
493
494 128 bits is considered to be cryptographically strong, but each
495 digit in a base 32 number can encode 5 bits, so 128 is rounded up
496 to the next multiple of 5.
497
498 This encoding is compact and efficient, with 5 random bits per
499 character. Compare this to a random UUID, which only has 3.4 bits
500 per character in standard layout, and only 122 random bits in total.
501
502 Note that SecureRandom objects are expensive to initialize, so
503 we'll want to keep it around and re-use it.
504 */
505
506 private String nextSessionId() {
507 return new BigInteger(130, random).toString(32);
508 }
509 }
510
Thomas Vachuska0af26912016-03-21 21:37:30 -0700511 // Auxiliary listener to preference map events.
512 private class InternalPrefsListener
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700513 implements MapEventListener<String, ObjectNode> {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700514 @Override
Madan Jampani7b93ceb2016-05-04 09:58:40 -0700515 public void event(MapEvent<String, ObjectNode> event) {
Madan Jampanibf8ee802016-05-04 14:07:36 -0700516 eventHandlingExecutor.execute(() -> {
517 String userName = userName(event.key());
518 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
519 UiWebSocketServlet.sendToUser(userName, UPDATE_PREFS, jsonPrefs());
520 }
521 });
Thomas Vachuska0af26912016-03-21 21:37:30 -0700522 }
523
524 private ObjectNode jsonPrefs() {
525 ObjectNode json = mapper.createObjectNode();
Simon Hunt1169c952017-06-05 11:20:11 -0700526 prefs.forEach((key, value) -> json.set(keyName(key), value));
Thomas Vachuska0af26912016-03-21 21:37:30 -0700527 return json;
528 }
529 }
Thomas Vachuskafe8c98a2015-02-04 01:24:32 -0800530}