blob: 62343914398a5c2787e374ea7dad7cd66f26dc72 [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;
Simon Hunt8eac4ae2017-01-20 12:56:45 -080047import org.onosproject.ui.model.topo.UiElement;
Simon Huntd5b96732016-07-08 13:22:27 -070048import org.onosproject.ui.model.topo.UiHost;
49import org.onosproject.ui.model.topo.UiLink;
Simon Hunt977aa052016-07-20 17:08:29 -070050import org.onosproject.ui.model.topo.UiNode;
Simon Huntd5b96732016-07-08 13:22:27 -070051import org.onosproject.ui.model.topo.UiRegion;
Simon Huntc13082f2016-08-03 21:20:23 -070052import org.onosproject.ui.model.topo.UiSynthLink;
Simon Huntd5b96732016-07-08 13:22:27 -070053import org.onosproject.ui.model.topo.UiTopoLayout;
Simon Hunt98189192016-07-29 19:02:27 -070054import org.slf4j.Logger;
55import org.slf4j.LoggerFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070056
Simon Hunt977aa052016-07-20 17:08:29 -070057import java.util.ArrayList;
58import java.util.HashMap;
59import java.util.HashSet;
Simon Huntd5b96732016-07-08 13:22:27 -070060import java.util.List;
Simon Hunt977aa052016-07-20 17:08:29 -070061import java.util.Map;
62import java.util.Set;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070063import java.util.concurrent.ConcurrentHashMap;
Simon Huntd5b96732016-07-08 13:22:27 -070064
65import static com.google.common.base.Preconditions.checkNotNull;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070066import static org.onosproject.net.AnnotationKeys.LATITUDE;
67import static org.onosproject.net.AnnotationKeys.LONGITUDE;
Simon Hunt977aa052016-07-20 17:08:29 -070068import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
Simon Huntd5b96732016-07-08 13:22:27 -070069
70/**
71 * Facility for creating JSON messages to send to the topology view in the
72 * Web client.
73 */
Simon Hunt537bc762016-12-20 12:15:13 -080074public class Topo2Jsonifier {
Simon Huntd5b96732016-07-08 13:22:27 -070075
Simon Hunt977aa052016-07-20 17:08:29 -070076 private static final String E_DEF_NOT_LAST =
77 "UiNode.LAYER_DEFAULT not last in layer list";
78 private static final String E_UNKNOWN_UI_NODE =
79 "Unknown subclass of UiNode: ";
80
Simon Hunt98189192016-07-29 19:02:27 -070081 private static final String REGION = "region";
82 private static final String DEVICE = "device";
83 private static final String HOST = "host";
Steven Burrows512b6272016-12-19 14:09:45 -050084 private static final String TYPE = "type";
85 private static final String SUBJECT = "subject";
Simon Hunt8eac4ae2017-01-20 12:56:45 -080086 private static final String DATA = "data";
87 private static final String MEMO = "memo";
Simon Hunt98189192016-07-29 19:02:27 -070088
89 private final Logger log = LoggerFactory.getLogger(getClass());
90
Simon Huntd5b96732016-07-08 13:22:27 -070091 private final ObjectMapper mapper = new ObjectMapper();
92
93 private ServiceDirectory directory;
94 private ClusterService clusterService;
95 private DeviceService deviceService;
96 private LinkService linkService;
97 private HostService hostService;
98 private MastershipService mastershipService;
99 private IntentService intentService;
100 private FlowRuleService flowService;
101 private StatisticService flowStatsService;
102 private PortStatisticsService portStatsService;
103 private TopologyService topologyService;
104 private TunnelService tunnelService;
105
106
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700107 // NOTE: we'll stick this here for now, but maybe there is a better home?
108 // (this is not distributed across the cluster)
109 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
110
111
Simon Huntd5b96732016-07-08 13:22:27 -0700112 /**
113 * Creates an instance with a reference to the services directory, so that
114 * additional information about network elements may be looked up on
115 * on the fly.
116 *
117 * @param directory service directory
118 */
Simon Hunt537bc762016-12-20 12:15:13 -0800119 public Topo2Jsonifier(ServiceDirectory directory) {
Simon Huntd5b96732016-07-08 13:22:27 -0700120 this.directory = checkNotNull(directory, "Directory cannot be null");
121
122 clusterService = directory.get(ClusterService.class);
123 deviceService = directory.get(DeviceService.class);
124 linkService = directory.get(LinkService.class);
125 hostService = directory.get(HostService.class);
126 mastershipService = directory.get(MastershipService.class);
127 intentService = directory.get(IntentService.class);
128 flowService = directory.get(FlowRuleService.class);
129 flowStatsService = directory.get(StatisticService.class);
130 portStatsService = directory.get(PortStatisticsService.class);
131 topologyService = directory.get(TopologyService.class);
132 tunnelService = directory.get(TunnelService.class);
Simon Hunt977aa052016-07-20 17:08:29 -0700133 }
Simon Huntd5b96732016-07-08 13:22:27 -0700134
Simon Hunt977aa052016-07-20 17:08:29 -0700135 // for unit testing
136 Topo2Jsonifier() {
Simon Huntd5b96732016-07-08 13:22:27 -0700137 }
138
139 private ObjectNode objectNode() {
140 return mapper.createObjectNode();
141 }
142
143 private ArrayNode arrayNode() {
144 return mapper.createArrayNode();
145 }
146
147 private String nullIsEmpty(Object o) {
148 return o == null ? "" : o.toString();
149 }
150
151
152 /**
153 * Returns a JSON representation of the cluster members (ONOS instances).
154 *
155 * @param instances the instance model objects
156 * @return a JSON representation of the data
157 */
158 ObjectNode instances(List<UiClusterMember> instances) {
159 NodeId local = clusterService.getLocalNode().id();
160 ObjectNode payload = objectNode();
161
162 ArrayNode members = arrayNode();
163 payload.set("members", members);
164 for (UiClusterMember member : instances) {
165 members.add(json(member, member.id().equals(local)));
166 }
167
168 return payload;
169 }
170
171 private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
Steven Burrowsad75aa22016-12-14 17:17:24 -0500172 int switchCount = mastershipService.getDevicesOf(member.id()).size();
Simon Huntd5b96732016-07-08 13:22:27 -0700173 return objectNode()
174 .put("id", member.id().toString())
175 .put("ip", member.ip().toString())
176 .put("online", member.isOnline())
177 .put("ready", member.isReady())
178 .put("uiAttached", isUiAttached)
Steven Burrowsad75aa22016-12-14 17:17:24 -0500179 .put("switches", switchCount);
Simon Huntd5b96732016-07-08 13:22:27 -0700180 }
181
182 /**
183 * Returns a JSON representation of the layout to use for displaying in
Simon Huntf836a872016-08-10 17:37:36 -0700184 * the topology view. The identifiers and names of regions from the
185 * current to the root is included, so that the bread-crumb widget can
186 * be rendered.
Simon Huntd5b96732016-07-08 13:22:27 -0700187 *
188 * @param layout the layout to transform
Simon Huntf836a872016-08-10 17:37:36 -0700189 * @param crumbs list of layouts in bread-crumb order
Simon Huntd5b96732016-07-08 13:22:27 -0700190 * @return a JSON representation of the data
191 */
Simon Huntf836a872016-08-10 17:37:36 -0700192 ObjectNode layout(UiTopoLayout layout, List<UiTopoLayout> crumbs) {
193 ObjectNode result = objectNode()
Simon Huntd5b96732016-07-08 13:22:27 -0700194 .put("id", layout.id().toString())
195 .put("parent", nullIsEmpty(layout.parent()))
196 .put("region", nullIsEmpty(layout.regionId()))
Simon Huntf836a872016-08-10 17:37:36 -0700197 .put("regionName", UiRegion.safeName(layout.region()));
198 addCrumbs(result, crumbs);
199 return result;
Simon Huntd5b96732016-07-08 13:22:27 -0700200 }
201
Simon Huntf836a872016-08-10 17:37:36 -0700202 private void addCrumbs(ObjectNode result, List<UiTopoLayout> crumbs) {
203 ArrayNode trail = arrayNode();
204 crumbs.forEach(c -> {
205 ObjectNode n = objectNode()
206 .put("id", c.regionId().toString())
207 .put("name", UiRegion.safeName(c.region()));
208 trail.add(n);
209 });
210 result.set("crumbs", trail);
Simon Huntd5b96732016-07-08 13:22:27 -0700211 }
212
213 /**
214 * Returns a JSON representation of the region to display in the topology
215 * view.
216 *
Simon Hunt977aa052016-07-20 17:08:29 -0700217 * @param region the region to transform to JSON
218 * @param subRegions the subregions within this region
Simon Huntc13082f2016-08-03 21:20:23 -0700219 * @param links the links within this region
Simon Huntd5b96732016-07-08 13:22:27 -0700220 * @return a JSON representation of the data
221 */
Simon Huntc13082f2016-08-03 21:20:23 -0700222 ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
223 List<UiSynthLink> links) {
Simon Huntd5b96732016-07-08 13:22:27 -0700224 ObjectNode payload = objectNode();
Simon Huntd5b96732016-07-08 13:22:27 -0700225 if (region == null) {
226 payload.put("note", "no-region");
227 return payload;
228 }
Simon Hunt977aa052016-07-20 17:08:29 -0700229 payload.put("id", region.idAsString());
Simon Huntcd508a62016-10-27 12:47:24 -0700230 payload.set("subregions", jsonSubRegions(subRegions));
231 payload.set("links", jsonLinks(links));
Simon Huntc13082f2016-08-03 21:20:23 -0700232
Simon Hunt977aa052016-07-20 17:08:29 -0700233 List<String> layerTags = region.layerOrder();
234 List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
235 List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Simon Huntd5b96732016-07-08 13:22:27 -0700236
Simon Hunt977aa052016-07-20 17:08:29 -0700237 payload.set("devices", jsonGrouped(splitDevices));
238 payload.set("hosts", jsonGrouped(splitHosts));
Simon Hunt977aa052016-07-20 17:08:29 -0700239 payload.set("layerOrder", jsonStrings(layerTags));
Simon Huntd5b96732016-07-08 13:22:27 -0700240
241 return payload;
242 }
243
Simon Hunt977aa052016-07-20 17:08:29 -0700244 private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
245 ArrayNode kids = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700246 subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
Simon Hunt977aa052016-07-20 17:08:29 -0700247 return kids;
248 }
249
Simon Huntc13082f2016-08-03 21:20:23 -0700250 private JsonNode jsonLinks(List<UiSynthLink> links) {
251 ArrayNode synthLinks = arrayNode();
252 links.forEach(l -> synthLinks.add(json(l)));
253 return synthLinks;
254 }
255
Simon Hunt977aa052016-07-20 17:08:29 -0700256 private ArrayNode jsonStrings(List<String> strings) {
257 ArrayNode array = arrayNode();
258 strings.forEach(array::add);
259 return array;
260 }
261
Simon Hunt977aa052016-07-20 17:08:29 -0700262 private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
263 ArrayNode result = arrayNode();
264 groupedNodes.forEach(g -> {
265 ArrayNode subset = arrayNode();
266 g.forEach(n -> subset.add(json(n)));
267 result.add(subset);
268 });
269 return result;
270 }
271
Simon Hunt537bc762016-12-20 12:15:13 -0800272 /**
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800273 * Creates a JSON representation of a UI element.
274 *
275 * @param element the source element
276 * @return a JSON representation of that element
277 */
278 public ObjectNode jsonUiElement(UiElement element) {
279 if (element instanceof UiNode) {
280 return json((UiNode) element);
281 }
282 if (element instanceof UiLink) {
283 return json((UiLink) element);
284 }
285
286 // TODO: UiClusterMember
287
288 // Unrecognized UiElement class
289 return objectNode()
290 .put("warning", "unknown UiElement... cannot encode")
291 .put("javaclass", element.getClass().toString());
292 }
293
294 /**
Simon Hunt537bc762016-12-20 12:15:13 -0800295 * Creates a JSON representation of a UI model event.
296 *
297 * @param modelEvent the source model event
298 * @return a JSON representation of that event
299 */
300 public ObjectNode jsonEvent(UiModelEvent modelEvent) {
Steven Burrows512b6272016-12-19 14:09:45 -0500301 ObjectNode payload = objectNode();
302 payload.put(TYPE, enumToString(modelEvent.type()));
303 payload.put(SUBJECT, modelEvent.subject().idAsString());
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800304 payload.set(DATA, modelEvent.data());
305 payload.put(MEMO, modelEvent.memo());
Steven Burrows512b6272016-12-19 14:09:45 -0500306 return payload;
307 }
308
309 // TODO: Investigate why we can't do this inline
310 private String enumToString(Enum<?> e) {
311 return e.toString();
312 }
313
Steven Burrowsad75aa22016-12-14 17:17:24 -0500314 // Returns the name of the master node for the specified device id.
315 private String master(DeviceId deviceId) {
316 NodeId master = mastershipService.getMasterFor(deviceId);
317 return master != null ? master.toString() : "";
318 }
Simon Hunt977aa052016-07-20 17:08:29 -0700319
320 private ObjectNode json(UiNode node) {
321 if (node instanceof UiRegion) {
322 return jsonClosedRegion((UiRegion) node);
323 }
324 if (node instanceof UiDevice) {
325 return json((UiDevice) node);
326 }
327 if (node instanceof UiHost) {
328 return json((UiHost) node);
329 }
330 throw new IllegalStateException(E_UNKNOWN_UI_NODE + node.getClass());
331 }
332
Simon Huntd5b96732016-07-08 13:22:27 -0700333 private ObjectNode json(UiDevice device) {
334 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700335 .put("id", device.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700336 .put("nodeType", DEVICE)
Simon Huntd5b96732016-07-08 13:22:27 -0700337 .put("type", device.type())
Simon Hunt3d712522016-08-11 11:20:44 -0700338 .put("online", deviceService.isAvailable(device.id()))
Steven Burrowsad75aa22016-12-14 17:17:24 -0500339 .put("master", master(device.id()))
Simon Huntd5b96732016-07-08 13:22:27 -0700340 .put("layer", device.layer());
341
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700342 Device d = device.backingDevice();
343
344 addProps(node, d);
345 addGeoLocation(node, d);
346 addMetaUi(node, device.idAsString());
Simon Huntd5b96732016-07-08 13:22:27 -0700347
348 return node;
349 }
350
Simon Hunt53612212016-12-04 17:19:52 -0800351 private void addProps(ObjectNode node, Annotated a) {
352 Annotations annot = a.annotations();
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700353 ObjectNode props = objectNode();
354 if (annot != null) {
355 annot.keys().forEach(k -> props.put(k, annot.value(k)));
356 }
357 node.set("props", props);
358 }
Simon Huntd5b96732016-07-08 13:22:27 -0700359
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700360 private void addMetaUi(ObjectNode node, String metaInstanceId) {
361 ObjectNode meta = metaUi.get(metaInstanceId);
362 if (meta != null) {
363 node.set("metaUi", meta);
364 }
365 }
366
367 private void addGeoLocation(ObjectNode node, Annotated a) {
368 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
369 if (lngLat != null) {
370 try {
371 double lng = Double.parseDouble(lngLat.get(0));
372 double lat = Double.parseDouble(lngLat.get(1));
373 ObjectNode loc = objectNode()
374 .put("type", "lnglat")
375 .put("lng", lng)
376 .put("lat", lat);
377 node.set("location", loc);
378
379 } catch (NumberFormatException e) {
380 log.warn("Invalid geo data: longitude={}, latitude={}",
381 lngLat.get(0), lngLat.get(1));
382 }
383 } else {
384 log.debug("No geo lng/lat for {}", a);
385 }
386 }
387
Steven Burrows583f4be2016-11-04 14:06:50 +0100388 private void addIps(ObjectNode node, Host h) {
389 Set<IpAddress> ips = h.ipAddresses();
390
391 ArrayNode a = arrayNode();
392 for (IpAddress ip : ips) {
393 a.add(ip.toString());
394 }
395
396 node.set("ips", a);
397 }
398
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700399 // return list of string values from annotated instance, for given keys
400 // return null if any keys are not present
401 List<String> getAnnotValues(Annotated a, String... annotKeys) {
402 List<String> result = new ArrayList<>(annotKeys.length);
403 for (String k : annotKeys) {
404 String v = a.annotations().value(k);
405 if (v == null) {
406 return null;
407 }
408 result.add(v);
409 }
410 return result;
411 }
412
413 // derive JSON object from annotations
414 private ObjectNode props(Annotations annotations) {
415 ObjectNode p = objectNode();
416 if (annotations != null) {
417 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
418 }
419 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700420 }
421
422 private ObjectNode json(UiHost host) {
Steven Burrows583f4be2016-11-04 14:06:50 +0100423 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700424 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700425 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700426 .put("layer", host.layer());
427 // TODO: complete host details
Steven Burrows583f4be2016-11-04 14:06:50 +0100428 Host h = host.backingHost();
429
430 addIps(node, h);
431 addGeoLocation(node, h);
432 addMetaUi(node, host.idAsString());
433
434 return node;
Simon Huntd5b96732016-07-08 13:22:27 -0700435 }
436
Simon Huntc13082f2016-08-03 21:20:23 -0700437 private ObjectNode json(UiSynthLink sLink) {
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800438 return json(sLink.link());
439 }
440
441 private ObjectNode json(UiLink link) {
Simon Hunt3d712522016-08-11 11:20:44 -0700442 ObjectNode data = objectNode()
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800443 .put("id", link.idAsString())
444 .put("epA", link.endPointA())
445 .put("epB", link.endPointB())
446 .put("type", link.type());
447 String pA = link.endPortA();
448 String pB = link.endPortB();
Simon Hunt3d712522016-08-11 11:20:44 -0700449 if (pA != null) {
450 data.put("portA", pA);
451 }
452 if (pB != null) {
453 data.put("portB", pB);
454 }
455 return data;
Simon Huntd5b96732016-07-08 13:22:27 -0700456 }
457
458
Simon Hunt977aa052016-07-20 17:08:29 -0700459 private ObjectNode jsonClosedRegion(UiRegion region) {
Steven Burrows482d9502016-09-27 11:24:58 -0700460 ObjectNode node = objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700461 .put("id", region.idAsString())
Simon Huntf836a872016-08-10 17:37:36 -0700462 .put("name", region.name())
Simon Hunt98189192016-07-29 19:02:27 -0700463 .put("nodeType", REGION)
Steven Burrows19e6e4f2016-10-05 13:27:07 -0500464 .put("nDevs", region.deviceCount())
465 .put("nHosts", region.hostCount());
Simon Hunt53612212016-12-04 17:19:52 -0800466
467 Region r = region.backingRegion();
468 addGeoLocation(node, r);
469 addProps(node, r);
Steven Burrows482d9502016-09-27 11:24:58 -0700470
471 addMetaUi(node, region.idAsString());
472 return node;
Simon Hunt977aa052016-07-20 17:08:29 -0700473 }
474
Simon Hunt98189192016-07-29 19:02:27 -0700475 /**
476 * Returns a JSON array representation of a set of regions/devices. Note
477 * that the information is sufficient for showing regions as nodes.
478 *
479 * @param nodes the nodes
480 * @return a JSON representation of the nodes
481 */
482 public ArrayNode closedNodes(Set<UiNode> nodes) {
483 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700484 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700485 if (node instanceof UiRegion) {
486 array.add(jsonClosedRegion((UiRegion) node));
487 } else if (node instanceof UiDevice) {
488 array.add(json((UiDevice) node));
489 } else {
490 log.warn("Unexpected node instance: {}", node.getClass());
491 }
492 }
493 return array;
494 }
Simon Hunt977aa052016-07-20 17:08:29 -0700495
496 /**
497 * Returns a JSON array representation of a list of regions. Note that the
498 * information about each region is limited to what needs to be used to
499 * show the regions as nodes on the view.
500 *
501 * @param regions the regions
502 * @return a JSON representation of the minimal region information
503 */
504 public ArrayNode closedRegions(Set<UiRegion> regions) {
505 ArrayNode array = arrayNode();
506 for (UiRegion r : regions) {
507 array.add(jsonClosedRegion(r));
508 }
509 return array;
510 }
511
512 /**
513 * Returns a JSON array representation of a list of devices.
514 *
515 * @param devices the devices
516 * @return a JSON representation of the devices
517 */
518 public ArrayNode devices(Set<UiDevice> devices) {
519 ArrayNode array = arrayNode();
520 for (UiDevice device : devices) {
521 array.add(json(device));
522 }
523 return array;
524 }
525
526 /**
527 * Returns a JSON array representation of a list of hosts.
528 *
529 * @param hosts the hosts
530 * @return a JSON representation of the hosts
531 */
532 public ArrayNode hosts(Set<UiHost> hosts) {
533 ArrayNode array = arrayNode();
534 for (UiHost host : hosts) {
535 array.add(json(host));
536 }
537 return array;
538 }
539
Simon Hunt977aa052016-07-20 17:08:29 -0700540 // package-private for unit testing
541 List<Set<UiNode>> splitByLayer(List<String> layerTags,
542 Set<? extends UiNode> nodes) {
543 final int nLayers = layerTags.size();
544 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
545 throw new IllegalArgumentException(E_DEF_NOT_LAST);
546 }
547
548 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
549 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
550
551 for (String tag : layerTags) {
552 Set<UiNode> set = new HashSet<>();
553 byLayer.put(tag, set);
554 splitList.add(set);
555 }
556
557 for (UiNode n : nodes) {
558 String which = n.layer();
559 if (!layerTags.contains(which)) {
560 which = LAYER_DEFAULT;
561 }
562 byLayer.get(which).add(n);
563 }
564
565 return splitList;
566 }
Steven Burrowse7cc3082016-09-27 11:24:58 -0700567
568 /**
569 * Stores the memento for an element.
570 * This method assumes the payload has an id String, memento ObjectNode
571 *
572 * @param payload event payload
573 */
574 void updateMeta(ObjectNode payload) {
575
576 String id = JsonUtils.string(payload, "id");
577 metaUi.put(id, JsonUtils.node(payload, "memento"));
578
579 log.debug("Storing metadata for {}", id);
580 }
Simon Huntd5b96732016-07-08 13:22:27 -0700581}