blob: fe59dce47093ade6b9ec793db859898167c335ca [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 Huntbc30e682017-02-15 18:39:23 -080066import static org.onosproject.net.AnnotationKeys.GRID_X;
67import static org.onosproject.net.AnnotationKeys.GRID_Y;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070068import static org.onosproject.net.AnnotationKeys.LATITUDE;
69import static org.onosproject.net.AnnotationKeys.LONGITUDE;
Simon Hunt977aa052016-07-20 17:08:29 -070070import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
Simon Huntd5b96732016-07-08 13:22:27 -070071
72/**
73 * Facility for creating JSON messages to send to the topology view in the
74 * Web client.
75 */
Simon Hunt537bc762016-12-20 12:15:13 -080076public class Topo2Jsonifier {
Simon Huntd5b96732016-07-08 13:22:27 -070077
Simon Hunt977aa052016-07-20 17:08:29 -070078 private static final String E_DEF_NOT_LAST =
79 "UiNode.LAYER_DEFAULT not last in layer list";
80 private static final String E_UNKNOWN_UI_NODE =
81 "Unknown subclass of UiNode: ";
82
Simon Hunt98189192016-07-29 19:02:27 -070083 private static final String REGION = "region";
84 private static final String DEVICE = "device";
85 private static final String HOST = "host";
Steven Burrows512b6272016-12-19 14:09:45 -050086 private static final String TYPE = "type";
87 private static final String SUBJECT = "subject";
Simon Hunt8eac4ae2017-01-20 12:56:45 -080088 private static final String DATA = "data";
89 private static final String MEMO = "memo";
Simon Hunt98189192016-07-29 19:02:27 -070090
91 private final Logger log = LoggerFactory.getLogger(getClass());
92
Simon Huntd5b96732016-07-08 13:22:27 -070093 private final ObjectMapper mapper = new ObjectMapper();
94
95 private ServiceDirectory directory;
96 private ClusterService clusterService;
97 private DeviceService deviceService;
98 private LinkService linkService;
99 private HostService hostService;
100 private MastershipService mastershipService;
101 private IntentService intentService;
102 private FlowRuleService flowService;
103 private StatisticService flowStatsService;
104 private PortStatisticsService portStatsService;
105 private TopologyService topologyService;
106 private TunnelService tunnelService;
107
108
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700109 // NOTE: we'll stick this here for now, but maybe there is a better home?
110 // (this is not distributed across the cluster)
111 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
112
113
Simon Huntd5b96732016-07-08 13:22:27 -0700114 /**
115 * Creates an instance with a reference to the services directory, so that
116 * additional information about network elements may be looked up on
117 * on the fly.
118 *
119 * @param directory service directory
120 */
Simon Hunt537bc762016-12-20 12:15:13 -0800121 public Topo2Jsonifier(ServiceDirectory directory) {
Simon Huntd5b96732016-07-08 13:22:27 -0700122 this.directory = checkNotNull(directory, "Directory cannot be null");
123
124 clusterService = directory.get(ClusterService.class);
125 deviceService = directory.get(DeviceService.class);
126 linkService = directory.get(LinkService.class);
127 hostService = directory.get(HostService.class);
128 mastershipService = directory.get(MastershipService.class);
129 intentService = directory.get(IntentService.class);
130 flowService = directory.get(FlowRuleService.class);
131 flowStatsService = directory.get(StatisticService.class);
132 portStatsService = directory.get(PortStatisticsService.class);
133 topologyService = directory.get(TopologyService.class);
134 tunnelService = directory.get(TunnelService.class);
Simon Hunt977aa052016-07-20 17:08:29 -0700135 }
Simon Huntd5b96732016-07-08 13:22:27 -0700136
Simon Hunt977aa052016-07-20 17:08:29 -0700137 // for unit testing
138 Topo2Jsonifier() {
Simon Huntd5b96732016-07-08 13:22:27 -0700139 }
140
141 private ObjectNode objectNode() {
142 return mapper.createObjectNode();
143 }
144
145 private ArrayNode arrayNode() {
146 return mapper.createArrayNode();
147 }
148
149 private String nullIsEmpty(Object o) {
150 return o == null ? "" : o.toString();
151 }
152
153
154 /**
155 * Returns a JSON representation of the cluster members (ONOS instances).
156 *
157 * @param instances the instance model objects
158 * @return a JSON representation of the data
159 */
160 ObjectNode instances(List<UiClusterMember> instances) {
161 NodeId local = clusterService.getLocalNode().id();
162 ObjectNode payload = objectNode();
163
164 ArrayNode members = arrayNode();
165 payload.set("members", members);
166 for (UiClusterMember member : instances) {
167 members.add(json(member, member.id().equals(local)));
168 }
169
170 return payload;
171 }
172
173 private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
Steven Burrowsad75aa22016-12-14 17:17:24 -0500174 int switchCount = mastershipService.getDevicesOf(member.id()).size();
Simon Huntd5b96732016-07-08 13:22:27 -0700175 return objectNode()
176 .put("id", member.id().toString())
177 .put("ip", member.ip().toString())
178 .put("online", member.isOnline())
179 .put("ready", member.isReady())
180 .put("uiAttached", isUiAttached)
Steven Burrowsad75aa22016-12-14 17:17:24 -0500181 .put("switches", switchCount);
Simon Huntd5b96732016-07-08 13:22:27 -0700182 }
183
184 /**
185 * Returns a JSON representation of the layout to use for displaying in
Simon Huntf836a872016-08-10 17:37:36 -0700186 * the topology view. The identifiers and names of regions from the
187 * current to the root is included, so that the bread-crumb widget can
188 * be rendered.
Simon Huntd5b96732016-07-08 13:22:27 -0700189 *
190 * @param layout the layout to transform
Simon Huntf836a872016-08-10 17:37:36 -0700191 * @param crumbs list of layouts in bread-crumb order
Simon Huntd5b96732016-07-08 13:22:27 -0700192 * @return a JSON representation of the data
193 */
Simon Huntf836a872016-08-10 17:37:36 -0700194 ObjectNode layout(UiTopoLayout layout, List<UiTopoLayout> crumbs) {
195 ObjectNode result = objectNode()
Simon Huntd5b96732016-07-08 13:22:27 -0700196 .put("id", layout.id().toString())
197 .put("parent", nullIsEmpty(layout.parent()))
198 .put("region", nullIsEmpty(layout.regionId()))
Simon Huntf836a872016-08-10 17:37:36 -0700199 .put("regionName", UiRegion.safeName(layout.region()));
200 addCrumbs(result, crumbs);
Simon Huntbc30e682017-02-15 18:39:23 -0800201 addBgRef(result, layout);
Simon Huntf836a872016-08-10 17:37:36 -0700202 return result;
Simon Huntd5b96732016-07-08 13:22:27 -0700203 }
204
Simon Huntbc30e682017-02-15 18:39:23 -0800205 private void addBgRef(ObjectNode result, UiTopoLayout layout) {
206 String map = layout.geomap();
207 String spr = layout.sprites();
208 if (map != null) {
209 result.put("bgType", "geo").put("bgId", map);
210 } else if (spr != null) {
211 result.put("bgType", "grid").put("bgId", spr);
212 }
213 }
214
Simon Huntf836a872016-08-10 17:37:36 -0700215 private void addCrumbs(ObjectNode result, List<UiTopoLayout> crumbs) {
216 ArrayNode trail = arrayNode();
217 crumbs.forEach(c -> {
218 ObjectNode n = objectNode()
219 .put("id", c.regionId().toString())
220 .put("name", UiRegion.safeName(c.region()));
221 trail.add(n);
222 });
223 result.set("crumbs", trail);
Simon Huntd5b96732016-07-08 13:22:27 -0700224 }
225
226 /**
227 * Returns a JSON representation of the region to display in the topology
228 * view.
229 *
Simon Hunt977aa052016-07-20 17:08:29 -0700230 * @param region the region to transform to JSON
231 * @param subRegions the subregions within this region
Simon Huntc13082f2016-08-03 21:20:23 -0700232 * @param links the links within this region
Simon Huntd5b96732016-07-08 13:22:27 -0700233 * @return a JSON representation of the data
234 */
Simon Huntc13082f2016-08-03 21:20:23 -0700235 ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
236 List<UiSynthLink> links) {
Simon Huntd5b96732016-07-08 13:22:27 -0700237 ObjectNode payload = objectNode();
Simon Huntd5b96732016-07-08 13:22:27 -0700238 if (region == null) {
239 payload.put("note", "no-region");
240 return payload;
241 }
Simon Hunt977aa052016-07-20 17:08:29 -0700242 payload.put("id", region.idAsString());
Simon Huntcd508a62016-10-27 12:47:24 -0700243 payload.set("subregions", jsonSubRegions(subRegions));
244 payload.set("links", jsonLinks(links));
Simon Huntc13082f2016-08-03 21:20:23 -0700245
Simon Hunt977aa052016-07-20 17:08:29 -0700246 List<String> layerTags = region.layerOrder();
247 List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
248 List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Simon Huntd5b96732016-07-08 13:22:27 -0700249
Simon Hunt977aa052016-07-20 17:08:29 -0700250 payload.set("devices", jsonGrouped(splitDevices));
251 payload.set("hosts", jsonGrouped(splitHosts));
Simon Hunt977aa052016-07-20 17:08:29 -0700252 payload.set("layerOrder", jsonStrings(layerTags));
Simon Huntd5b96732016-07-08 13:22:27 -0700253
254 return payload;
255 }
256
Simon Hunt977aa052016-07-20 17:08:29 -0700257 private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
258 ArrayNode kids = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700259 subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
Simon Hunt977aa052016-07-20 17:08:29 -0700260 return kids;
261 }
262
Simon Huntc13082f2016-08-03 21:20:23 -0700263 private JsonNode jsonLinks(List<UiSynthLink> links) {
264 ArrayNode synthLinks = arrayNode();
265 links.forEach(l -> synthLinks.add(json(l)));
266 return synthLinks;
267 }
268
Simon Hunt977aa052016-07-20 17:08:29 -0700269 private ArrayNode jsonStrings(List<String> strings) {
270 ArrayNode array = arrayNode();
271 strings.forEach(array::add);
272 return array;
273 }
274
Simon Hunt977aa052016-07-20 17:08:29 -0700275 private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
276 ArrayNode result = arrayNode();
277 groupedNodes.forEach(g -> {
278 ArrayNode subset = arrayNode();
279 g.forEach(n -> subset.add(json(n)));
280 result.add(subset);
281 });
282 return result;
283 }
284
Simon Hunt537bc762016-12-20 12:15:13 -0800285 /**
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800286 * Creates a JSON representation of a UI element.
287 *
288 * @param element the source element
289 * @return a JSON representation of that element
290 */
291 public ObjectNode jsonUiElement(UiElement element) {
292 if (element instanceof UiNode) {
293 return json((UiNode) element);
294 }
295 if (element instanceof UiLink) {
296 return json((UiLink) element);
297 }
298
299 // TODO: UiClusterMember
300
301 // Unrecognized UiElement class
302 return objectNode()
303 .put("warning", "unknown UiElement... cannot encode")
304 .put("javaclass", element.getClass().toString());
305 }
306
307 /**
Simon Hunt537bc762016-12-20 12:15:13 -0800308 * Creates a JSON representation of a UI model event.
309 *
310 * @param modelEvent the source model event
311 * @return a JSON representation of that event
312 */
313 public ObjectNode jsonEvent(UiModelEvent modelEvent) {
Steven Burrows512b6272016-12-19 14:09:45 -0500314 ObjectNode payload = objectNode();
315 payload.put(TYPE, enumToString(modelEvent.type()));
316 payload.put(SUBJECT, modelEvent.subject().idAsString());
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800317 payload.set(DATA, modelEvent.data());
318 payload.put(MEMO, modelEvent.memo());
Steven Burrows512b6272016-12-19 14:09:45 -0500319 return payload;
320 }
321
322 // TODO: Investigate why we can't do this inline
323 private String enumToString(Enum<?> e) {
324 return e.toString();
325 }
326
Steven Burrowsad75aa22016-12-14 17:17:24 -0500327 // Returns the name of the master node for the specified device id.
328 private String master(DeviceId deviceId) {
329 NodeId master = mastershipService.getMasterFor(deviceId);
330 return master != null ? master.toString() : "";
331 }
Simon Hunt977aa052016-07-20 17:08:29 -0700332
333 private ObjectNode json(UiNode node) {
334 if (node instanceof UiRegion) {
335 return jsonClosedRegion((UiRegion) node);
336 }
337 if (node instanceof UiDevice) {
338 return json((UiDevice) node);
339 }
340 if (node instanceof UiHost) {
341 return json((UiHost) node);
342 }
343 throw new IllegalStateException(E_UNKNOWN_UI_NODE + node.getClass());
344 }
345
Simon Huntd5b96732016-07-08 13:22:27 -0700346 private ObjectNode json(UiDevice device) {
347 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700348 .put("id", device.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700349 .put("nodeType", DEVICE)
Simon Huntd5b96732016-07-08 13:22:27 -0700350 .put("type", device.type())
Simon Hunt3d712522016-08-11 11:20:44 -0700351 .put("online", deviceService.isAvailable(device.id()))
Steven Burrowsad75aa22016-12-14 17:17:24 -0500352 .put("master", master(device.id()))
Simon Huntd5b96732016-07-08 13:22:27 -0700353 .put("layer", device.layer());
354
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700355 Device d = device.backingDevice();
356
357 addProps(node, d);
Simon Huntbc30e682017-02-15 18:39:23 -0800358 addGeoGridLocation(node, d);
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700359 addMetaUi(node, device.idAsString());
Simon Huntd5b96732016-07-08 13:22:27 -0700360
361 return node;
362 }
363
Simon Hunt53612212016-12-04 17:19:52 -0800364 private void addProps(ObjectNode node, Annotated a) {
365 Annotations annot = a.annotations();
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700366 ObjectNode props = objectNode();
367 if (annot != null) {
368 annot.keys().forEach(k -> props.put(k, annot.value(k)));
369 }
370 node.set("props", props);
371 }
Simon Huntd5b96732016-07-08 13:22:27 -0700372
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700373 private void addMetaUi(ObjectNode node, String metaInstanceId) {
374 ObjectNode meta = metaUi.get(metaInstanceId);
375 if (meta != null) {
376 node.set("metaUi", meta);
377 }
378 }
379
Simon Huntbc30e682017-02-15 18:39:23 -0800380 private void addGeoGridLocation(ObjectNode node, Annotated a) {
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700381 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
Simon Huntbc30e682017-02-15 18:39:23 -0800382 List<String> gridYX = getAnnotValues(a, GRID_Y, GRID_X);
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700383
Simon Huntbc30e682017-02-15 18:39:23 -0800384 if (lngLat != null) {
385 attachLocation(node, "geo", "lng", "lat", lngLat);
386 } else if (gridYX != null) {
387 attachLocation(node, "grid", "gridY", "gridX", gridYX);
388 }
389 }
390
391 private void attachLocation(ObjectNode node, String locType,
392 String keyA, String keyB, List<String> values) {
393 try {
394 double valA = Double.parseDouble(values.get(0));
395 double valB = Double.parseDouble(values.get(1));
396 ObjectNode loc = objectNode()
397 .put("type", locType)
398 .put(keyA, valA)
399 .put(keyB, valB);
400 node.set("location", loc);
401
402 } catch (NumberFormatException e) {
403 log.warn("Invalid {} data: long/Y={}, lat/X={}",
404 locType, values.get(0), values.get(1));
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700405 }
406 }
407
Steven Burrows583f4be2016-11-04 14:06:50 +0100408 private void addIps(ObjectNode node, Host h) {
409 Set<IpAddress> ips = h.ipAddresses();
410
411 ArrayNode a = arrayNode();
412 for (IpAddress ip : ips) {
413 a.add(ip.toString());
414 }
415
416 node.set("ips", a);
417 }
418
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700419 // return list of string values from annotated instance, for given keys
420 // return null if any keys are not present
421 List<String> getAnnotValues(Annotated a, String... annotKeys) {
422 List<String> result = new ArrayList<>(annotKeys.length);
423 for (String k : annotKeys) {
424 String v = a.annotations().value(k);
425 if (v == null) {
426 return null;
427 }
428 result.add(v);
429 }
430 return result;
431 }
432
433 // derive JSON object from annotations
434 private ObjectNode props(Annotations annotations) {
435 ObjectNode p = objectNode();
436 if (annotations != null) {
437 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
438 }
439 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700440 }
441
442 private ObjectNode json(UiHost host) {
Steven Burrows583f4be2016-11-04 14:06:50 +0100443 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700444 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700445 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700446 .put("layer", host.layer());
447 // TODO: complete host details
Steven Burrows583f4be2016-11-04 14:06:50 +0100448 Host h = host.backingHost();
449
450 addIps(node, h);
Simon Huntbc30e682017-02-15 18:39:23 -0800451 addProps(node, h);
452 addGeoGridLocation(node, h);
Steven Burrows583f4be2016-11-04 14:06:50 +0100453 addMetaUi(node, host.idAsString());
454
455 return node;
Simon Huntd5b96732016-07-08 13:22:27 -0700456 }
457
Simon Huntc13082f2016-08-03 21:20:23 -0700458 private ObjectNode json(UiSynthLink sLink) {
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800459 return json(sLink.link());
460 }
461
462 private ObjectNode json(UiLink link) {
Simon Hunt3d712522016-08-11 11:20:44 -0700463 ObjectNode data = objectNode()
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800464 .put("id", link.idAsString())
465 .put("epA", link.endPointA())
466 .put("epB", link.endPointB())
467 .put("type", link.type());
468 String pA = link.endPortA();
469 String pB = link.endPortB();
Simon Hunt3d712522016-08-11 11:20:44 -0700470 if (pA != null) {
471 data.put("portA", pA);
472 }
473 if (pB != null) {
474 data.put("portB", pB);
475 }
476 return data;
Simon Huntd5b96732016-07-08 13:22:27 -0700477 }
478
479
Simon Hunt977aa052016-07-20 17:08:29 -0700480 private ObjectNode jsonClosedRegion(UiRegion region) {
Steven Burrows482d9502016-09-27 11:24:58 -0700481 ObjectNode node = objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700482 .put("id", region.idAsString())
Simon Huntf836a872016-08-10 17:37:36 -0700483 .put("name", region.name())
Simon Hunt98189192016-07-29 19:02:27 -0700484 .put("nodeType", REGION)
Steven Burrows19e6e4f2016-10-05 13:27:07 -0500485 .put("nDevs", region.deviceCount())
486 .put("nHosts", region.hostCount());
Simon Hunt53612212016-12-04 17:19:52 -0800487
488 Region r = region.backingRegion();
Simon Huntbc30e682017-02-15 18:39:23 -0800489 addGeoGridLocation(node, r);
Simon Hunt53612212016-12-04 17:19:52 -0800490 addProps(node, r);
Steven Burrows482d9502016-09-27 11:24:58 -0700491
492 addMetaUi(node, region.idAsString());
493 return node;
Simon Hunt977aa052016-07-20 17:08:29 -0700494 }
495
Simon Hunt98189192016-07-29 19:02:27 -0700496 /**
497 * Returns a JSON array representation of a set of regions/devices. Note
498 * that the information is sufficient for showing regions as nodes.
499 *
500 * @param nodes the nodes
501 * @return a JSON representation of the nodes
502 */
503 public ArrayNode closedNodes(Set<UiNode> nodes) {
504 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700505 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700506 if (node instanceof UiRegion) {
507 array.add(jsonClosedRegion((UiRegion) node));
508 } else if (node instanceof UiDevice) {
509 array.add(json((UiDevice) node));
510 } else {
511 log.warn("Unexpected node instance: {}", node.getClass());
512 }
513 }
514 return array;
515 }
Simon Hunt977aa052016-07-20 17:08:29 -0700516
517 /**
518 * Returns a JSON array representation of a list of regions. Note that the
519 * information about each region is limited to what needs to be used to
520 * show the regions as nodes on the view.
521 *
522 * @param regions the regions
523 * @return a JSON representation of the minimal region information
524 */
525 public ArrayNode closedRegions(Set<UiRegion> regions) {
526 ArrayNode array = arrayNode();
527 for (UiRegion r : regions) {
528 array.add(jsonClosedRegion(r));
529 }
530 return array;
531 }
532
533 /**
534 * Returns a JSON array representation of a list of devices.
535 *
536 * @param devices the devices
537 * @return a JSON representation of the devices
538 */
539 public ArrayNode devices(Set<UiDevice> devices) {
540 ArrayNode array = arrayNode();
541 for (UiDevice device : devices) {
542 array.add(json(device));
543 }
544 return array;
545 }
546
547 /**
548 * Returns a JSON array representation of a list of hosts.
549 *
550 * @param hosts the hosts
551 * @return a JSON representation of the hosts
552 */
553 public ArrayNode hosts(Set<UiHost> hosts) {
554 ArrayNode array = arrayNode();
555 for (UiHost host : hosts) {
556 array.add(json(host));
557 }
558 return array;
559 }
560
Simon Hunt977aa052016-07-20 17:08:29 -0700561 // package-private for unit testing
562 List<Set<UiNode>> splitByLayer(List<String> layerTags,
563 Set<? extends UiNode> nodes) {
564 final int nLayers = layerTags.size();
565 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
566 throw new IllegalArgumentException(E_DEF_NOT_LAST);
567 }
568
569 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
570 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
571
572 for (String tag : layerTags) {
573 Set<UiNode> set = new HashSet<>();
574 byLayer.put(tag, set);
575 splitList.add(set);
576 }
577
578 for (UiNode n : nodes) {
579 String which = n.layer();
580 if (!layerTags.contains(which)) {
581 which = LAYER_DEFAULT;
582 }
583 byLayer.get(which).add(n);
584 }
585
586 return splitList;
587 }
Steven Burrowse7cc3082016-09-27 11:24:58 -0700588
589 /**
590 * Stores the memento for an element.
591 * This method assumes the payload has an id String, memento ObjectNode
592 *
593 * @param payload event payload
594 */
595 void updateMeta(ObjectNode payload) {
596
597 String id = JsonUtils.string(payload, "id");
598 metaUi.put(id, JsonUtils.node(payload, "memento"));
599
600 log.debug("Storing metadata for {}", id);
601 }
Simon Huntd5b96732016-07-08 13:22:27 -0700602}