blob: ebc20fe177055a832b675884ed4005223dc81398 [file] [log] [blame]
Simon Huntd5b96732016-07-08 13:22:27 -07001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
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 */
16
17package org.onosproject.ui.impl.topo;
18
Simon Huntc13082f2016-08-03 21:20:23 -070019import com.fasterxml.jackson.databind.JsonNode;
Simon Huntd5b96732016-07-08 13:22:27 -070020import com.fasterxml.jackson.databind.ObjectMapper;
21import com.fasterxml.jackson.databind.node.ArrayNode;
22import com.fasterxml.jackson.databind.node.ObjectNode;
23import org.onlab.osgi.ServiceDirectory;
Steven Burrows583f4be2016-11-04 14:06:50 +010024import org.onlab.packet.IpAddress;
Simon Huntd5b96732016-07-08 13:22:27 -070025import org.onosproject.cluster.ClusterService;
26import org.onosproject.cluster.NodeId;
27import org.onosproject.incubator.net.PortStatisticsService;
28import org.onosproject.incubator.net.tunnel.TunnelService;
29import org.onosproject.mastership.MastershipService;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070030import org.onosproject.net.Annotated;
31import org.onosproject.net.Annotations;
32import org.onosproject.net.Device;
Steven Burrowsad75aa22016-12-14 17:17:24 -050033import org.onosproject.net.DeviceId;
Steven Burrows583f4be2016-11-04 14:06:50 +010034import org.onosproject.net.Host;
Simon Huntd5b96732016-07-08 13:22:27 -070035import org.onosproject.net.device.DeviceService;
36import org.onosproject.net.flow.FlowRuleService;
37import org.onosproject.net.host.HostService;
38import org.onosproject.net.intent.IntentService;
39import org.onosproject.net.link.LinkService;
Simon Hunt53612212016-12-04 17:19:52 -080040import org.onosproject.net.region.Region;
Simon Huntd5b96732016-07-08 13:22:27 -070041import org.onosproject.net.statistic.StatisticService;
42import org.onosproject.net.topology.TopologyService;
Steven Burrowse7cc3082016-09-27 11:24:58 -070043import org.onosproject.ui.JsonUtils;
Steven Burrows512b6272016-12-19 14:09:45 -050044import org.onosproject.ui.impl.topo.model.UiModelEvent;
Simon Huntd5b96732016-07-08 13:22:27 -070045import org.onosproject.ui.model.topo.UiClusterMember;
46import org.onosproject.ui.model.topo.UiDevice;
47import org.onosproject.ui.model.topo.UiHost;
48import org.onosproject.ui.model.topo.UiLink;
Simon Hunt977aa052016-07-20 17:08:29 -070049import org.onosproject.ui.model.topo.UiNode;
Simon Huntd5b96732016-07-08 13:22:27 -070050import org.onosproject.ui.model.topo.UiRegion;
Simon Huntc13082f2016-08-03 21:20:23 -070051import org.onosproject.ui.model.topo.UiSynthLink;
Simon Huntd5b96732016-07-08 13:22:27 -070052import org.onosproject.ui.model.topo.UiTopoLayout;
Simon Hunt98189192016-07-29 19:02:27 -070053import org.slf4j.Logger;
54import org.slf4j.LoggerFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070055
Simon Hunt977aa052016-07-20 17:08:29 -070056import java.util.ArrayList;
57import java.util.HashMap;
58import java.util.HashSet;
Simon Huntd5b96732016-07-08 13:22:27 -070059import java.util.List;
Simon Hunt977aa052016-07-20 17:08:29 -070060import java.util.Map;
61import java.util.Set;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070062import java.util.concurrent.ConcurrentHashMap;
Simon Huntd5b96732016-07-08 13:22:27 -070063
64import static com.google.common.base.Preconditions.checkNotNull;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070065import static org.onosproject.net.AnnotationKeys.LATITUDE;
66import static org.onosproject.net.AnnotationKeys.LONGITUDE;
Simon Hunt977aa052016-07-20 17:08:29 -070067import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
Simon Huntd5b96732016-07-08 13:22:27 -070068
69/**
70 * Facility for creating JSON messages to send to the topology view in the
71 * Web client.
72 */
Simon Hunt537bc762016-12-20 12:15:13 -080073public class Topo2Jsonifier {
Simon Huntd5b96732016-07-08 13:22:27 -070074
Simon Hunt977aa052016-07-20 17:08:29 -070075 private static final String E_DEF_NOT_LAST =
76 "UiNode.LAYER_DEFAULT not last in layer list";
77 private static final String E_UNKNOWN_UI_NODE =
78 "Unknown subclass of UiNode: ";
79
Simon Hunt98189192016-07-29 19:02:27 -070080 private static final String REGION = "region";
81 private static final String DEVICE = "device";
82 private static final String HOST = "host";
Steven Burrows512b6272016-12-19 14:09:45 -050083 private static final String TYPE = "type";
84 private static final String SUBJECT = "subject";
Simon Hunt98189192016-07-29 19:02:27 -070085
86 private final Logger log = LoggerFactory.getLogger(getClass());
87
Simon Huntd5b96732016-07-08 13:22:27 -070088 private final ObjectMapper mapper = new ObjectMapper();
89
90 private ServiceDirectory directory;
91 private ClusterService clusterService;
92 private DeviceService deviceService;
93 private LinkService linkService;
94 private HostService hostService;
95 private MastershipService mastershipService;
96 private IntentService intentService;
97 private FlowRuleService flowService;
98 private StatisticService flowStatsService;
99 private PortStatisticsService portStatsService;
100 private TopologyService topologyService;
101 private TunnelService tunnelService;
102
103
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700104 // NOTE: we'll stick this here for now, but maybe there is a better home?
105 // (this is not distributed across the cluster)
106 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
107
108
Simon Huntd5b96732016-07-08 13:22:27 -0700109 /**
110 * Creates an instance with a reference to the services directory, so that
111 * additional information about network elements may be looked up on
112 * on the fly.
113 *
114 * @param directory service directory
115 */
Simon Hunt537bc762016-12-20 12:15:13 -0800116 public Topo2Jsonifier(ServiceDirectory directory) {
Simon Huntd5b96732016-07-08 13:22:27 -0700117 this.directory = checkNotNull(directory, "Directory cannot be null");
118
119 clusterService = directory.get(ClusterService.class);
120 deviceService = directory.get(DeviceService.class);
121 linkService = directory.get(LinkService.class);
122 hostService = directory.get(HostService.class);
123 mastershipService = directory.get(MastershipService.class);
124 intentService = directory.get(IntentService.class);
125 flowService = directory.get(FlowRuleService.class);
126 flowStatsService = directory.get(StatisticService.class);
127 portStatsService = directory.get(PortStatisticsService.class);
128 topologyService = directory.get(TopologyService.class);
129 tunnelService = directory.get(TunnelService.class);
Simon Hunt977aa052016-07-20 17:08:29 -0700130 }
Simon Huntd5b96732016-07-08 13:22:27 -0700131
Simon Hunt977aa052016-07-20 17:08:29 -0700132 // for unit testing
133 Topo2Jsonifier() {
Simon Huntd5b96732016-07-08 13:22:27 -0700134 }
135
136 private ObjectNode objectNode() {
137 return mapper.createObjectNode();
138 }
139
140 private ArrayNode arrayNode() {
141 return mapper.createArrayNode();
142 }
143
144 private String nullIsEmpty(Object o) {
145 return o == null ? "" : o.toString();
146 }
147
148
149 /**
150 * Returns a JSON representation of the cluster members (ONOS instances).
151 *
152 * @param instances the instance model objects
153 * @return a JSON representation of the data
154 */
155 ObjectNode instances(List<UiClusterMember> instances) {
156 NodeId local = clusterService.getLocalNode().id();
157 ObjectNode payload = objectNode();
158
159 ArrayNode members = arrayNode();
160 payload.set("members", members);
161 for (UiClusterMember member : instances) {
162 members.add(json(member, member.id().equals(local)));
163 }
164
165 return payload;
166 }
167
168 private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
Steven Burrowsad75aa22016-12-14 17:17:24 -0500169 int switchCount = mastershipService.getDevicesOf(member.id()).size();
Simon Huntd5b96732016-07-08 13:22:27 -0700170 return objectNode()
171 .put("id", member.id().toString())
172 .put("ip", member.ip().toString())
173 .put("online", member.isOnline())
174 .put("ready", member.isReady())
175 .put("uiAttached", isUiAttached)
Steven Burrowsad75aa22016-12-14 17:17:24 -0500176 .put("switches", switchCount);
Simon Huntd5b96732016-07-08 13:22:27 -0700177 }
178
179 /**
180 * Returns a JSON representation of the layout to use for displaying in
Simon Huntf836a872016-08-10 17:37:36 -0700181 * the topology view. The identifiers and names of regions from the
182 * current to the root is included, so that the bread-crumb widget can
183 * be rendered.
Simon Huntd5b96732016-07-08 13:22:27 -0700184 *
185 * @param layout the layout to transform
Simon Huntf836a872016-08-10 17:37:36 -0700186 * @param crumbs list of layouts in bread-crumb order
Simon Huntd5b96732016-07-08 13:22:27 -0700187 * @return a JSON representation of the data
188 */
Simon Huntf836a872016-08-10 17:37:36 -0700189 ObjectNode layout(UiTopoLayout layout, List<UiTopoLayout> crumbs) {
190 ObjectNode result = objectNode()
Simon Huntd5b96732016-07-08 13:22:27 -0700191 .put("id", layout.id().toString())
192 .put("parent", nullIsEmpty(layout.parent()))
193 .put("region", nullIsEmpty(layout.regionId()))
Simon Huntf836a872016-08-10 17:37:36 -0700194 .put("regionName", UiRegion.safeName(layout.region()));
195 addCrumbs(result, crumbs);
196 return result;
Simon Huntd5b96732016-07-08 13:22:27 -0700197 }
198
Simon Huntf836a872016-08-10 17:37:36 -0700199 private void addCrumbs(ObjectNode result, List<UiTopoLayout> crumbs) {
200 ArrayNode trail = arrayNode();
201 crumbs.forEach(c -> {
202 ObjectNode n = objectNode()
203 .put("id", c.regionId().toString())
204 .put("name", UiRegion.safeName(c.region()));
205 trail.add(n);
206 });
207 result.set("crumbs", trail);
Simon Huntd5b96732016-07-08 13:22:27 -0700208 }
209
210 /**
211 * Returns a JSON representation of the region to display in the topology
212 * view.
213 *
Simon Hunt977aa052016-07-20 17:08:29 -0700214 * @param region the region to transform to JSON
215 * @param subRegions the subregions within this region
Simon Huntc13082f2016-08-03 21:20:23 -0700216 * @param links the links within this region
Simon Huntd5b96732016-07-08 13:22:27 -0700217 * @return a JSON representation of the data
218 */
Simon Huntc13082f2016-08-03 21:20:23 -0700219 ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
220 List<UiSynthLink> links) {
Simon Huntd5b96732016-07-08 13:22:27 -0700221 ObjectNode payload = objectNode();
Simon Huntd5b96732016-07-08 13:22:27 -0700222 if (region == null) {
223 payload.put("note", "no-region");
224 return payload;
225 }
Simon Hunt977aa052016-07-20 17:08:29 -0700226 payload.put("id", region.idAsString());
Simon Huntcd508a62016-10-27 12:47:24 -0700227 payload.set("subregions", jsonSubRegions(subRegions));
228 payload.set("links", jsonLinks(links));
Simon Huntc13082f2016-08-03 21:20:23 -0700229
Simon Hunt977aa052016-07-20 17:08:29 -0700230 List<String> layerTags = region.layerOrder();
231 List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
232 List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Simon Huntd5b96732016-07-08 13:22:27 -0700233
Simon Hunt977aa052016-07-20 17:08:29 -0700234 payload.set("devices", jsonGrouped(splitDevices));
235 payload.set("hosts", jsonGrouped(splitHosts));
Simon Hunt977aa052016-07-20 17:08:29 -0700236 payload.set("layerOrder", jsonStrings(layerTags));
Simon Huntd5b96732016-07-08 13:22:27 -0700237
238 return payload;
239 }
240
Simon Hunt977aa052016-07-20 17:08:29 -0700241 private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
242 ArrayNode kids = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700243 subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
Simon Hunt977aa052016-07-20 17:08:29 -0700244 return kids;
245 }
246
Simon Huntc13082f2016-08-03 21:20:23 -0700247 private JsonNode jsonLinks(List<UiSynthLink> links) {
248 ArrayNode synthLinks = arrayNode();
249 links.forEach(l -> synthLinks.add(json(l)));
250 return synthLinks;
251 }
252
Simon Hunt977aa052016-07-20 17:08:29 -0700253 private ArrayNode jsonStrings(List<String> strings) {
254 ArrayNode array = arrayNode();
255 strings.forEach(array::add);
256 return array;
257 }
258
Simon Hunt977aa052016-07-20 17:08:29 -0700259 private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
260 ArrayNode result = arrayNode();
261 groupedNodes.forEach(g -> {
262 ArrayNode subset = arrayNode();
263 g.forEach(n -> subset.add(json(n)));
264 result.add(subset);
265 });
266 return result;
267 }
268
Simon Hunt537bc762016-12-20 12:15:13 -0800269 /**
270 * Creates a JSON representation of a UI model event.
271 *
272 * @param modelEvent the source model event
273 * @return a JSON representation of that event
274 */
275 public ObjectNode jsonEvent(UiModelEvent modelEvent) {
Steven Burrows512b6272016-12-19 14:09:45 -0500276 ObjectNode payload = objectNode();
277 payload.put(TYPE, enumToString(modelEvent.type()));
278 payload.put(SUBJECT, modelEvent.subject().idAsString());
279 return payload;
280 }
281
282 // TODO: Investigate why we can't do this inline
283 private String enumToString(Enum<?> e) {
284 return e.toString();
285 }
286
Steven Burrowsad75aa22016-12-14 17:17:24 -0500287 // Returns the name of the master node for the specified device id.
288 private String master(DeviceId deviceId) {
289 NodeId master = mastershipService.getMasterFor(deviceId);
290 return master != null ? master.toString() : "";
291 }
Simon Hunt977aa052016-07-20 17:08:29 -0700292
293 private ObjectNode json(UiNode node) {
294 if (node instanceof UiRegion) {
295 return jsonClosedRegion((UiRegion) node);
296 }
297 if (node instanceof UiDevice) {
298 return json((UiDevice) node);
299 }
300 if (node instanceof UiHost) {
301 return json((UiHost) node);
302 }
303 throw new IllegalStateException(E_UNKNOWN_UI_NODE + node.getClass());
304 }
305
Simon Huntd5b96732016-07-08 13:22:27 -0700306 private ObjectNode json(UiDevice device) {
307 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700308 .put("id", device.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700309 .put("nodeType", DEVICE)
Simon Huntd5b96732016-07-08 13:22:27 -0700310 .put("type", device.type())
Simon Hunt3d712522016-08-11 11:20:44 -0700311 .put("online", deviceService.isAvailable(device.id()))
Steven Burrowsad75aa22016-12-14 17:17:24 -0500312 .put("master", master(device.id()))
Simon Huntd5b96732016-07-08 13:22:27 -0700313 .put("layer", device.layer());
314
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700315 Device d = device.backingDevice();
316
317 addProps(node, d);
318 addGeoLocation(node, d);
319 addMetaUi(node, device.idAsString());
Simon Huntd5b96732016-07-08 13:22:27 -0700320
321 return node;
322 }
323
Simon Hunt53612212016-12-04 17:19:52 -0800324 private void addProps(ObjectNode node, Annotated a) {
325 Annotations annot = a.annotations();
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700326 ObjectNode props = objectNode();
327 if (annot != null) {
328 annot.keys().forEach(k -> props.put(k, annot.value(k)));
329 }
330 node.set("props", props);
331 }
Simon Huntd5b96732016-07-08 13:22:27 -0700332
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700333 private void addMetaUi(ObjectNode node, String metaInstanceId) {
334 ObjectNode meta = metaUi.get(metaInstanceId);
335 if (meta != null) {
336 node.set("metaUi", meta);
337 }
338 }
339
340 private void addGeoLocation(ObjectNode node, Annotated a) {
341 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
342 if (lngLat != null) {
343 try {
344 double lng = Double.parseDouble(lngLat.get(0));
345 double lat = Double.parseDouble(lngLat.get(1));
346 ObjectNode loc = objectNode()
347 .put("type", "lnglat")
348 .put("lng", lng)
349 .put("lat", lat);
350 node.set("location", loc);
351
352 } catch (NumberFormatException e) {
353 log.warn("Invalid geo data: longitude={}, latitude={}",
354 lngLat.get(0), lngLat.get(1));
355 }
356 } else {
357 log.debug("No geo lng/lat for {}", a);
358 }
359 }
360
Steven Burrows583f4be2016-11-04 14:06:50 +0100361 private void addIps(ObjectNode node, Host h) {
362 Set<IpAddress> ips = h.ipAddresses();
363
364 ArrayNode a = arrayNode();
365 for (IpAddress ip : ips) {
366 a.add(ip.toString());
367 }
368
369 node.set("ips", a);
370 }
371
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700372 // return list of string values from annotated instance, for given keys
373 // return null if any keys are not present
374 List<String> getAnnotValues(Annotated a, String... annotKeys) {
375 List<String> result = new ArrayList<>(annotKeys.length);
376 for (String k : annotKeys) {
377 String v = a.annotations().value(k);
378 if (v == null) {
379 return null;
380 }
381 result.add(v);
382 }
383 return result;
384 }
385
386 // derive JSON object from annotations
387 private ObjectNode props(Annotations annotations) {
388 ObjectNode p = objectNode();
389 if (annotations != null) {
390 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
391 }
392 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700393 }
394
395 private ObjectNode json(UiHost host) {
Steven Burrows583f4be2016-11-04 14:06:50 +0100396 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700397 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700398 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700399 .put("layer", host.layer());
400 // TODO: complete host details
Steven Burrows583f4be2016-11-04 14:06:50 +0100401 Host h = host.backingHost();
402
403 addIps(node, h);
404 addGeoLocation(node, h);
405 addMetaUi(node, host.idAsString());
406
407 return node;
Simon Huntd5b96732016-07-08 13:22:27 -0700408 }
409
Simon Huntc13082f2016-08-03 21:20:23 -0700410 private ObjectNode json(UiSynthLink sLink) {
411 UiLink uLink = sLink.link();
Simon Hunt3d712522016-08-11 11:20:44 -0700412 ObjectNode data = objectNode()
Simon Huntc13082f2016-08-03 21:20:23 -0700413 .put("id", uLink.idAsString())
414 .put("epA", uLink.endPointA())
415 .put("epB", uLink.endPointB())
416 .put("type", uLink.type());
Simon Hunt3d712522016-08-11 11:20:44 -0700417 String pA = uLink.endPortA();
418 String pB = uLink.endPortB();
419 if (pA != null) {
420 data.put("portA", pA);
421 }
422 if (pB != null) {
423 data.put("portB", pB);
424 }
425 return data;
Simon Huntd5b96732016-07-08 13:22:27 -0700426 }
427
428
Simon Hunt977aa052016-07-20 17:08:29 -0700429 private ObjectNode jsonClosedRegion(UiRegion region) {
Steven Burrows482d9502016-09-27 11:24:58 -0700430 ObjectNode node = objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700431 .put("id", region.idAsString())
Simon Huntf836a872016-08-10 17:37:36 -0700432 .put("name", region.name())
Simon Hunt98189192016-07-29 19:02:27 -0700433 .put("nodeType", REGION)
Steven Burrows19e6e4f2016-10-05 13:27:07 -0500434 .put("nDevs", region.deviceCount())
435 .put("nHosts", region.hostCount());
Simon Hunt53612212016-12-04 17:19:52 -0800436
437 Region r = region.backingRegion();
438 addGeoLocation(node, r);
439 addProps(node, r);
Steven Burrows482d9502016-09-27 11:24:58 -0700440
441 addMetaUi(node, region.idAsString());
442 return node;
Simon Hunt977aa052016-07-20 17:08:29 -0700443 }
444
Simon Hunt98189192016-07-29 19:02:27 -0700445 /**
446 * Returns a JSON array representation of a set of regions/devices. Note
447 * that the information is sufficient for showing regions as nodes.
448 *
449 * @param nodes the nodes
450 * @return a JSON representation of the nodes
451 */
452 public ArrayNode closedNodes(Set<UiNode> nodes) {
453 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700454 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700455 if (node instanceof UiRegion) {
456 array.add(jsonClosedRegion((UiRegion) node));
457 } else if (node instanceof UiDevice) {
458 array.add(json((UiDevice) node));
459 } else {
460 log.warn("Unexpected node instance: {}", node.getClass());
461 }
462 }
463 return array;
464 }
Simon Hunt977aa052016-07-20 17:08:29 -0700465
466 /**
467 * Returns a JSON array representation of a list of regions. Note that the
468 * information about each region is limited to what needs to be used to
469 * show the regions as nodes on the view.
470 *
471 * @param regions the regions
472 * @return a JSON representation of the minimal region information
473 */
474 public ArrayNode closedRegions(Set<UiRegion> regions) {
475 ArrayNode array = arrayNode();
476 for (UiRegion r : regions) {
477 array.add(jsonClosedRegion(r));
478 }
479 return array;
480 }
481
482 /**
483 * Returns a JSON array representation of a list of devices.
484 *
485 * @param devices the devices
486 * @return a JSON representation of the devices
487 */
488 public ArrayNode devices(Set<UiDevice> devices) {
489 ArrayNode array = arrayNode();
490 for (UiDevice device : devices) {
491 array.add(json(device));
492 }
493 return array;
494 }
495
496 /**
497 * Returns a JSON array representation of a list of hosts.
498 *
499 * @param hosts the hosts
500 * @return a JSON representation of the hosts
501 */
502 public ArrayNode hosts(Set<UiHost> hosts) {
503 ArrayNode array = arrayNode();
504 for (UiHost host : hosts) {
505 array.add(json(host));
506 }
507 return array;
508 }
509
Simon Hunt977aa052016-07-20 17:08:29 -0700510 // package-private for unit testing
511 List<Set<UiNode>> splitByLayer(List<String> layerTags,
512 Set<? extends UiNode> nodes) {
513 final int nLayers = layerTags.size();
514 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
515 throw new IllegalArgumentException(E_DEF_NOT_LAST);
516 }
517
518 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
519 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
520
521 for (String tag : layerTags) {
522 Set<UiNode> set = new HashSet<>();
523 byLayer.put(tag, set);
524 splitList.add(set);
525 }
526
527 for (UiNode n : nodes) {
528 String which = n.layer();
529 if (!layerTags.contains(which)) {
530 which = LAYER_DEFAULT;
531 }
532 byLayer.get(which).add(n);
533 }
534
535 return splitList;
536 }
Steven Burrowse7cc3082016-09-27 11:24:58 -0700537
538 /**
539 * Stores the memento for an element.
540 * This method assumes the payload has an id String, memento ObjectNode
541 *
542 * @param payload event payload
543 */
544 void updateMeta(ObjectNode payload) {
545
546 String id = JsonUtils.string(payload, "id");
547 metaUi.put(id, JsonUtils.node(payload, "memento"));
548
549 log.debug("Storing metadata for {}", id);
550 }
Simon Huntd5b96732016-07-08 13:22:27 -0700551}