blob: 8db72aa2e2b990a5c1146702776267cbfe0fd91f [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
Simon Hunteb3cf542017-02-10 13:18:41 -0800367 // FIXME: need to handle geo vs. grid location...
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700368 private void addGeoLocation(ObjectNode node, Annotated a) {
369 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
370 if (lngLat != null) {
371 try {
372 double lng = Double.parseDouble(lngLat.get(0));
373 double lat = Double.parseDouble(lngLat.get(1));
374 ObjectNode loc = objectNode()
375 .put("type", "lnglat")
376 .put("lng", lng)
377 .put("lat", lat);
378 node.set("location", loc);
379
380 } catch (NumberFormatException e) {
381 log.warn("Invalid geo data: longitude={}, latitude={}",
382 lngLat.get(0), lngLat.get(1));
383 }
384 } else {
385 log.debug("No geo lng/lat for {}", a);
386 }
387 }
388
Steven Burrows583f4be2016-11-04 14:06:50 +0100389 private void addIps(ObjectNode node, Host h) {
390 Set<IpAddress> ips = h.ipAddresses();
391
392 ArrayNode a = arrayNode();
393 for (IpAddress ip : ips) {
394 a.add(ip.toString());
395 }
396
397 node.set("ips", a);
398 }
399
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700400 // return list of string values from annotated instance, for given keys
401 // return null if any keys are not present
402 List<String> getAnnotValues(Annotated a, String... annotKeys) {
403 List<String> result = new ArrayList<>(annotKeys.length);
404 for (String k : annotKeys) {
405 String v = a.annotations().value(k);
406 if (v == null) {
407 return null;
408 }
409 result.add(v);
410 }
411 return result;
412 }
413
414 // derive JSON object from annotations
415 private ObjectNode props(Annotations annotations) {
416 ObjectNode p = objectNode();
417 if (annotations != null) {
418 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
419 }
420 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700421 }
422
423 private ObjectNode json(UiHost host) {
Steven Burrows583f4be2016-11-04 14:06:50 +0100424 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700425 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700426 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700427 .put("layer", host.layer());
428 // TODO: complete host details
Steven Burrows583f4be2016-11-04 14:06:50 +0100429 Host h = host.backingHost();
430
431 addIps(node, h);
432 addGeoLocation(node, h);
433 addMetaUi(node, host.idAsString());
434
435 return node;
Simon Huntd5b96732016-07-08 13:22:27 -0700436 }
437
Simon Huntc13082f2016-08-03 21:20:23 -0700438 private ObjectNode json(UiSynthLink sLink) {
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800439 return json(sLink.link());
440 }
441
442 private ObjectNode json(UiLink link) {
Simon Hunt3d712522016-08-11 11:20:44 -0700443 ObjectNode data = objectNode()
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800444 .put("id", link.idAsString())
445 .put("epA", link.endPointA())
446 .put("epB", link.endPointB())
447 .put("type", link.type());
448 String pA = link.endPortA();
449 String pB = link.endPortB();
Simon Hunt3d712522016-08-11 11:20:44 -0700450 if (pA != null) {
451 data.put("portA", pA);
452 }
453 if (pB != null) {
454 data.put("portB", pB);
455 }
456 return data;
Simon Huntd5b96732016-07-08 13:22:27 -0700457 }
458
459
Simon Hunt977aa052016-07-20 17:08:29 -0700460 private ObjectNode jsonClosedRegion(UiRegion region) {
Steven Burrows482d9502016-09-27 11:24:58 -0700461 ObjectNode node = objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700462 .put("id", region.idAsString())
Simon Huntf836a872016-08-10 17:37:36 -0700463 .put("name", region.name())
Simon Hunt98189192016-07-29 19:02:27 -0700464 .put("nodeType", REGION)
Steven Burrows19e6e4f2016-10-05 13:27:07 -0500465 .put("nDevs", region.deviceCount())
466 .put("nHosts", region.hostCount());
Simon Hunt53612212016-12-04 17:19:52 -0800467
468 Region r = region.backingRegion();
469 addGeoLocation(node, r);
470 addProps(node, r);
Steven Burrows482d9502016-09-27 11:24:58 -0700471
472 addMetaUi(node, region.idAsString());
473 return node;
Simon Hunt977aa052016-07-20 17:08:29 -0700474 }
475
Simon Hunt98189192016-07-29 19:02:27 -0700476 /**
477 * Returns a JSON array representation of a set of regions/devices. Note
478 * that the information is sufficient for showing regions as nodes.
479 *
480 * @param nodes the nodes
481 * @return a JSON representation of the nodes
482 */
483 public ArrayNode closedNodes(Set<UiNode> nodes) {
484 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700485 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700486 if (node instanceof UiRegion) {
487 array.add(jsonClosedRegion((UiRegion) node));
488 } else if (node instanceof UiDevice) {
489 array.add(json((UiDevice) node));
490 } else {
491 log.warn("Unexpected node instance: {}", node.getClass());
492 }
493 }
494 return array;
495 }
Simon Hunt977aa052016-07-20 17:08:29 -0700496
497 /**
498 * Returns a JSON array representation of a list of regions. Note that the
499 * information about each region is limited to what needs to be used to
500 * show the regions as nodes on the view.
501 *
502 * @param regions the regions
503 * @return a JSON representation of the minimal region information
504 */
505 public ArrayNode closedRegions(Set<UiRegion> regions) {
506 ArrayNode array = arrayNode();
507 for (UiRegion r : regions) {
508 array.add(jsonClosedRegion(r));
509 }
510 return array;
511 }
512
513 /**
514 * Returns a JSON array representation of a list of devices.
515 *
516 * @param devices the devices
517 * @return a JSON representation of the devices
518 */
519 public ArrayNode devices(Set<UiDevice> devices) {
520 ArrayNode array = arrayNode();
521 for (UiDevice device : devices) {
522 array.add(json(device));
523 }
524 return array;
525 }
526
527 /**
528 * Returns a JSON array representation of a list of hosts.
529 *
530 * @param hosts the hosts
531 * @return a JSON representation of the hosts
532 */
533 public ArrayNode hosts(Set<UiHost> hosts) {
534 ArrayNode array = arrayNode();
535 for (UiHost host : hosts) {
536 array.add(json(host));
537 }
538 return array;
539 }
540
Simon Hunt977aa052016-07-20 17:08:29 -0700541 // package-private for unit testing
542 List<Set<UiNode>> splitByLayer(List<String> layerTags,
543 Set<? extends UiNode> nodes) {
544 final int nLayers = layerTags.size();
545 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
546 throw new IllegalArgumentException(E_DEF_NOT_LAST);
547 }
548
549 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
550 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
551
552 for (String tag : layerTags) {
553 Set<UiNode> set = new HashSet<>();
554 byLayer.put(tag, set);
555 splitList.add(set);
556 }
557
558 for (UiNode n : nodes) {
559 String which = n.layer();
560 if (!layerTags.contains(which)) {
561 which = LAYER_DEFAULT;
562 }
563 byLayer.get(which).add(n);
564 }
565
566 return splitList;
567 }
Steven Burrowse7cc3082016-09-27 11:24:58 -0700568
569 /**
570 * Stores the memento for an element.
571 * This method assumes the payload has an id String, memento ObjectNode
572 *
573 * @param payload event payload
574 */
575 void updateMeta(ObjectNode payload) {
576
577 String id = JsonUtils.string(payload, "id");
578 metaUi.put(id, JsonUtils.node(payload, "memento"));
579
580 log.debug("Storing metadata for {}", id);
581 }
Simon Huntd5b96732016-07-08 13:22:27 -0700582}