blob: 4b12e68378e5ec0dd03993a46371e89c6c8b5d75 [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 Burrows86b74fc2017-02-22 00:15:16 +000044import org.onosproject.ui.UiExtensionService;
45import org.onosproject.ui.UiTopoMap;
46import org.onosproject.ui.UiTopoMapFactory;
Steven Burrows512b6272016-12-19 14:09:45 -050047import org.onosproject.ui.impl.topo.model.UiModelEvent;
Simon Huntd5b96732016-07-08 13:22:27 -070048import org.onosproject.ui.model.topo.UiClusterMember;
49import org.onosproject.ui.model.topo.UiDevice;
Simon Hunt8eac4ae2017-01-20 12:56:45 -080050import org.onosproject.ui.model.topo.UiElement;
Simon Huntd5b96732016-07-08 13:22:27 -070051import org.onosproject.ui.model.topo.UiHost;
52import org.onosproject.ui.model.topo.UiLink;
Simon Hunt977aa052016-07-20 17:08:29 -070053import org.onosproject.ui.model.topo.UiNode;
Simon Huntd5b96732016-07-08 13:22:27 -070054import org.onosproject.ui.model.topo.UiRegion;
Simon Huntc13082f2016-08-03 21:20:23 -070055import org.onosproject.ui.model.topo.UiSynthLink;
Simon Huntd5b96732016-07-08 13:22:27 -070056import org.onosproject.ui.model.topo.UiTopoLayout;
Simon Hunt98189192016-07-29 19:02:27 -070057import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
Simon Huntd5b96732016-07-08 13:22:27 -070059
Simon Hunt977aa052016-07-20 17:08:29 -070060import java.util.ArrayList;
61import java.util.HashMap;
62import java.util.HashSet;
Simon Huntd5b96732016-07-08 13:22:27 -070063import java.util.List;
Simon Hunt977aa052016-07-20 17:08:29 -070064import java.util.Map;
65import java.util.Set;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070066import java.util.concurrent.ConcurrentHashMap;
Simon Huntd5b96732016-07-08 13:22:27 -070067
68import static com.google.common.base.Preconditions.checkNotNull;
Simon Huntbc30e682017-02-15 18:39:23 -080069import static org.onosproject.net.AnnotationKeys.GRID_X;
70import static org.onosproject.net.AnnotationKeys.GRID_Y;
Simon Hunt6a8cb4f2016-08-09 15:08:57 -070071import static org.onosproject.net.AnnotationKeys.LATITUDE;
72import static org.onosproject.net.AnnotationKeys.LONGITUDE;
Simon Hunt977aa052016-07-20 17:08:29 -070073import static org.onosproject.ui.model.topo.UiNode.LAYER_DEFAULT;
Simon Huntd5b96732016-07-08 13:22:27 -070074
75/**
76 * Facility for creating JSON messages to send to the topology view in the
77 * Web client.
78 */
Simon Hunt537bc762016-12-20 12:15:13 -080079public class Topo2Jsonifier {
Simon Huntd5b96732016-07-08 13:22:27 -070080
Simon Hunt977aa052016-07-20 17:08:29 -070081 private static final String E_DEF_NOT_LAST =
82 "UiNode.LAYER_DEFAULT not last in layer list";
83 private static final String E_UNKNOWN_UI_NODE =
84 "Unknown subclass of UiNode: ";
85
Simon Hunt98189192016-07-29 19:02:27 -070086 private static final String REGION = "region";
87 private static final String DEVICE = "device";
88 private static final String HOST = "host";
Steven Burrows512b6272016-12-19 14:09:45 -050089 private static final String TYPE = "type";
90 private static final String SUBJECT = "subject";
Simon Hunt8eac4ae2017-01-20 12:56:45 -080091 private static final String DATA = "data";
92 private static final String MEMO = "memo";
Simon Hunt98189192016-07-29 19:02:27 -070093
94 private final Logger log = LoggerFactory.getLogger(getClass());
95
Simon Huntd5b96732016-07-08 13:22:27 -070096 private final ObjectMapper mapper = new ObjectMapper();
97
98 private ServiceDirectory directory;
99 private ClusterService clusterService;
100 private DeviceService deviceService;
101 private LinkService linkService;
102 private HostService hostService;
103 private MastershipService mastershipService;
104 private IntentService intentService;
105 private FlowRuleService flowService;
106 private StatisticService flowStatsService;
107 private PortStatisticsService portStatsService;
108 private TopologyService topologyService;
109 private TunnelService tunnelService;
Steven Burrows86b74fc2017-02-22 00:15:16 +0000110 private UiExtensionService uiextService;
Simon Huntd5b96732016-07-08 13:22:27 -0700111
112
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700113 // NOTE: we'll stick this here for now, but maybe there is a better home?
114 // (this is not distributed across the cluster)
115 private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
116
117
Simon Huntd5b96732016-07-08 13:22:27 -0700118 /**
119 * Creates an instance with a reference to the services directory, so that
120 * additional information about network elements may be looked up on
121 * on the fly.
122 *
123 * @param directory service directory
124 */
Simon Hunt537bc762016-12-20 12:15:13 -0800125 public Topo2Jsonifier(ServiceDirectory directory) {
Simon Huntd5b96732016-07-08 13:22:27 -0700126 this.directory = checkNotNull(directory, "Directory cannot be null");
127
128 clusterService = directory.get(ClusterService.class);
129 deviceService = directory.get(DeviceService.class);
130 linkService = directory.get(LinkService.class);
131 hostService = directory.get(HostService.class);
132 mastershipService = directory.get(MastershipService.class);
133 intentService = directory.get(IntentService.class);
134 flowService = directory.get(FlowRuleService.class);
135 flowStatsService = directory.get(StatisticService.class);
136 portStatsService = directory.get(PortStatisticsService.class);
137 topologyService = directory.get(TopologyService.class);
138 tunnelService = directory.get(TunnelService.class);
Steven Burrows86b74fc2017-02-22 00:15:16 +0000139 uiextService = directory.get(UiExtensionService.class);
Simon Hunt977aa052016-07-20 17:08:29 -0700140 }
Simon Huntd5b96732016-07-08 13:22:27 -0700141
Simon Hunt977aa052016-07-20 17:08:29 -0700142 // for unit testing
143 Topo2Jsonifier() {
Simon Huntd5b96732016-07-08 13:22:27 -0700144 }
145
146 private ObjectNode objectNode() {
147 return mapper.createObjectNode();
148 }
149
150 private ArrayNode arrayNode() {
151 return mapper.createArrayNode();
152 }
153
154 private String nullIsEmpty(Object o) {
155 return o == null ? "" : o.toString();
156 }
157
158
159 /**
160 * Returns a JSON representation of the cluster members (ONOS instances).
161 *
162 * @param instances the instance model objects
163 * @return a JSON representation of the data
164 */
165 ObjectNode instances(List<UiClusterMember> instances) {
166 NodeId local = clusterService.getLocalNode().id();
167 ObjectNode payload = objectNode();
168
169 ArrayNode members = arrayNode();
170 payload.set("members", members);
171 for (UiClusterMember member : instances) {
172 members.add(json(member, member.id().equals(local)));
173 }
174
175 return payload;
176 }
177
178 private ObjectNode json(UiClusterMember member, boolean isUiAttached) {
Steven Burrowsad75aa22016-12-14 17:17:24 -0500179 int switchCount = mastershipService.getDevicesOf(member.id()).size();
Simon Huntd5b96732016-07-08 13:22:27 -0700180 return objectNode()
181 .put("id", member.id().toString())
182 .put("ip", member.ip().toString())
183 .put("online", member.isOnline())
184 .put("ready", member.isReady())
185 .put("uiAttached", isUiAttached)
Steven Burrowsad75aa22016-12-14 17:17:24 -0500186 .put("switches", switchCount);
Simon Huntd5b96732016-07-08 13:22:27 -0700187 }
188
189 /**
190 * Returns a JSON representation of the layout to use for displaying in
Simon Huntf836a872016-08-10 17:37:36 -0700191 * the topology view. The identifiers and names of regions from the
192 * current to the root is included, so that the bread-crumb widget can
193 * be rendered.
Simon Huntd5b96732016-07-08 13:22:27 -0700194 *
195 * @param layout the layout to transform
Simon Huntf836a872016-08-10 17:37:36 -0700196 * @param crumbs list of layouts in bread-crumb order
Simon Huntd5b96732016-07-08 13:22:27 -0700197 * @return a JSON representation of the data
198 */
Simon Huntf836a872016-08-10 17:37:36 -0700199 ObjectNode layout(UiTopoLayout layout, List<UiTopoLayout> crumbs) {
200 ObjectNode result = objectNode()
Simon Huntd5b96732016-07-08 13:22:27 -0700201 .put("id", layout.id().toString())
202 .put("parent", nullIsEmpty(layout.parent()))
203 .put("region", nullIsEmpty(layout.regionId()))
Simon Huntf836a872016-08-10 17:37:36 -0700204 .put("regionName", UiRegion.safeName(layout.region()));
205 addCrumbs(result, crumbs);
Simon Huntbc30e682017-02-15 18:39:23 -0800206 addBgRef(result, layout);
Simon Huntf836a872016-08-10 17:37:36 -0700207 return result;
Simon Huntd5b96732016-07-08 13:22:27 -0700208 }
209
Simon Huntbc30e682017-02-15 18:39:23 -0800210 private void addBgRef(ObjectNode result, UiTopoLayout layout) {
Steven Burrows86b74fc2017-02-22 00:15:16 +0000211 String mapId = layout.geomap();
212 String sprId = layout.sprites();
213
214 if (mapId != null) {
215 result.put("bgType", "geo").put("bgId", mapId);
216 addMapParameters(result, mapId);
217 } else if (sprId != null) {
218 result.put("bgType", "grid").put("bgId", sprId);
219 }
220 }
221
222 private void addMapParameters(ObjectNode result, String mapId) {
223
224 // TODO: This ought to be written more efficiently.
225
226 // ALSO: Should retrieving a UiTopoMap by ID be something that
227 // the UiExtensionService provides, along with other
228 // useful lookups?
229 //
230 // Or should it remain very basic / general?
231 //
232 // return uiextService.getTopoMap(String mapId);
233
234 final UiTopoMap[] map = {null};
235
236 uiextService.getExtensions().forEach(ext -> {
237 UiTopoMapFactory factory = ext.topoMapFactory();
238
239 // TODO: use .stream().filter(...) here
240 if (map[0] == null && factory != null) {
241 List<UiTopoMap> topoMaps = factory.geoMaps();
242
243 topoMaps.forEach(m -> {
244 if (map[0] == null && m.id().equals(mapId)) {
245 map[0] = m;
246 }
247 });
248 }
249 });
250
251 UiTopoMap m = map[0];
252 if (m != null) {
253 result.put("bgDesc", m.description())
254 .put("bgFilePath", m.filePath())
255 .put("bgDefaultScale", m.scale());
256 } else {
257 result.put("bgWarn", "no map registered with id: " + mapId);
Simon Huntbc30e682017-02-15 18:39:23 -0800258 }
259 }
260
Simon Huntf836a872016-08-10 17:37:36 -0700261 private void addCrumbs(ObjectNode result, List<UiTopoLayout> crumbs) {
262 ArrayNode trail = arrayNode();
263 crumbs.forEach(c -> {
264 ObjectNode n = objectNode()
265 .put("id", c.regionId().toString())
266 .put("name", UiRegion.safeName(c.region()));
267 trail.add(n);
268 });
269 result.set("crumbs", trail);
Simon Huntd5b96732016-07-08 13:22:27 -0700270 }
271
272 /**
273 * Returns a JSON representation of the region to display in the topology
274 * view.
275 *
Simon Hunt977aa052016-07-20 17:08:29 -0700276 * @param region the region to transform to JSON
277 * @param subRegions the subregions within this region
Simon Huntc13082f2016-08-03 21:20:23 -0700278 * @param links the links within this region
Simon Huntd5b96732016-07-08 13:22:27 -0700279 * @return a JSON representation of the data
280 */
Simon Huntc13082f2016-08-03 21:20:23 -0700281 ObjectNode region(UiRegion region, Set<UiRegion> subRegions,
282 List<UiSynthLink> links) {
Simon Huntd5b96732016-07-08 13:22:27 -0700283 ObjectNode payload = objectNode();
Simon Huntd5b96732016-07-08 13:22:27 -0700284 if (region == null) {
285 payload.put("note", "no-region");
286 return payload;
287 }
Simon Hunt977aa052016-07-20 17:08:29 -0700288 payload.put("id", region.idAsString());
Simon Huntcd508a62016-10-27 12:47:24 -0700289 payload.set("subregions", jsonSubRegions(subRegions));
290 payload.set("links", jsonLinks(links));
Simon Huntc13082f2016-08-03 21:20:23 -0700291
Simon Hunt977aa052016-07-20 17:08:29 -0700292 List<String> layerTags = region.layerOrder();
293 List<Set<UiNode>> splitDevices = splitByLayer(layerTags, region.devices());
294 List<Set<UiNode>> splitHosts = splitByLayer(layerTags, region.hosts());
Simon Huntd5b96732016-07-08 13:22:27 -0700295
Simon Hunt977aa052016-07-20 17:08:29 -0700296 payload.set("devices", jsonGrouped(splitDevices));
297 payload.set("hosts", jsonGrouped(splitHosts));
Simon Hunt977aa052016-07-20 17:08:29 -0700298 payload.set("layerOrder", jsonStrings(layerTags));
Simon Huntd5b96732016-07-08 13:22:27 -0700299
300 return payload;
301 }
302
Simon Hunt977aa052016-07-20 17:08:29 -0700303 private ArrayNode jsonSubRegions(Set<UiRegion> subregions) {
304 ArrayNode kids = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700305 subregions.forEach(s -> kids.add(jsonClosedRegion(s)));
Simon Hunt977aa052016-07-20 17:08:29 -0700306 return kids;
307 }
308
Simon Huntc13082f2016-08-03 21:20:23 -0700309 private JsonNode jsonLinks(List<UiSynthLink> links) {
310 ArrayNode synthLinks = arrayNode();
311 links.forEach(l -> synthLinks.add(json(l)));
312 return synthLinks;
313 }
314
Simon Hunt977aa052016-07-20 17:08:29 -0700315 private ArrayNode jsonStrings(List<String> strings) {
316 ArrayNode array = arrayNode();
317 strings.forEach(array::add);
318 return array;
319 }
320
Simon Hunt977aa052016-07-20 17:08:29 -0700321 private ArrayNode jsonGrouped(List<Set<UiNode>> groupedNodes) {
322 ArrayNode result = arrayNode();
323 groupedNodes.forEach(g -> {
324 ArrayNode subset = arrayNode();
325 g.forEach(n -> subset.add(json(n)));
326 result.add(subset);
327 });
328 return result;
329 }
330
Simon Hunt537bc762016-12-20 12:15:13 -0800331 /**
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800332 * Creates a JSON representation of a UI element.
333 *
334 * @param element the source element
335 * @return a JSON representation of that element
336 */
337 public ObjectNode jsonUiElement(UiElement element) {
338 if (element instanceof UiNode) {
339 return json((UiNode) element);
340 }
341 if (element instanceof UiLink) {
342 return json((UiLink) element);
343 }
344
345 // TODO: UiClusterMember
346
347 // Unrecognized UiElement class
348 return objectNode()
349 .put("warning", "unknown UiElement... cannot encode")
350 .put("javaclass", element.getClass().toString());
351 }
352
353 /**
Simon Hunt537bc762016-12-20 12:15:13 -0800354 * Creates a JSON representation of a UI model event.
355 *
356 * @param modelEvent the source model event
357 * @return a JSON representation of that event
358 */
359 public ObjectNode jsonEvent(UiModelEvent modelEvent) {
Steven Burrows512b6272016-12-19 14:09:45 -0500360 ObjectNode payload = objectNode();
361 payload.put(TYPE, enumToString(modelEvent.type()));
362 payload.put(SUBJECT, modelEvent.subject().idAsString());
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800363 payload.set(DATA, modelEvent.data());
364 payload.put(MEMO, modelEvent.memo());
Steven Burrows512b6272016-12-19 14:09:45 -0500365 return payload;
366 }
367
368 // TODO: Investigate why we can't do this inline
369 private String enumToString(Enum<?> e) {
370 return e.toString();
371 }
372
Steven Burrowsad75aa22016-12-14 17:17:24 -0500373 // Returns the name of the master node for the specified device id.
374 private String master(DeviceId deviceId) {
375 NodeId master = mastershipService.getMasterFor(deviceId);
376 return master != null ? master.toString() : "";
377 }
Simon Hunt977aa052016-07-20 17:08:29 -0700378
379 private ObjectNode json(UiNode node) {
380 if (node instanceof UiRegion) {
381 return jsonClosedRegion((UiRegion) node);
382 }
383 if (node instanceof UiDevice) {
384 return json((UiDevice) node);
385 }
386 if (node instanceof UiHost) {
387 return json((UiHost) node);
388 }
389 throw new IllegalStateException(E_UNKNOWN_UI_NODE + node.getClass());
390 }
391
Simon Huntd5b96732016-07-08 13:22:27 -0700392 private ObjectNode json(UiDevice device) {
393 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700394 .put("id", device.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700395 .put("nodeType", DEVICE)
Simon Huntd5b96732016-07-08 13:22:27 -0700396 .put("type", device.type())
Simon Hunt3d712522016-08-11 11:20:44 -0700397 .put("online", deviceService.isAvailable(device.id()))
Steven Burrowsad75aa22016-12-14 17:17:24 -0500398 .put("master", master(device.id()))
Simon Huntd5b96732016-07-08 13:22:27 -0700399 .put("layer", device.layer());
400
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700401 Device d = device.backingDevice();
402
403 addProps(node, d);
Simon Huntbc30e682017-02-15 18:39:23 -0800404 addGeoGridLocation(node, d);
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700405 addMetaUi(node, device.idAsString());
Simon Huntd5b96732016-07-08 13:22:27 -0700406
407 return node;
408 }
409
Simon Hunt53612212016-12-04 17:19:52 -0800410 private void addProps(ObjectNode node, Annotated a) {
411 Annotations annot = a.annotations();
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700412 ObjectNode props = objectNode();
413 if (annot != null) {
414 annot.keys().forEach(k -> props.put(k, annot.value(k)));
415 }
416 node.set("props", props);
417 }
Simon Huntd5b96732016-07-08 13:22:27 -0700418
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700419 private void addMetaUi(ObjectNode node, String metaInstanceId) {
420 ObjectNode meta = metaUi.get(metaInstanceId);
421 if (meta != null) {
422 node.set("metaUi", meta);
423 }
424 }
425
Simon Huntbc30e682017-02-15 18:39:23 -0800426 private void addGeoGridLocation(ObjectNode node, Annotated a) {
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700427 List<String> lngLat = getAnnotValues(a, LONGITUDE, LATITUDE);
Simon Huntbc30e682017-02-15 18:39:23 -0800428 List<String> gridYX = getAnnotValues(a, GRID_Y, GRID_X);
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700429
Simon Huntbc30e682017-02-15 18:39:23 -0800430 if (lngLat != null) {
431 attachLocation(node, "geo", "lng", "lat", lngLat);
432 } else if (gridYX != null) {
433 attachLocation(node, "grid", "gridY", "gridX", gridYX);
434 }
435 }
436
437 private void attachLocation(ObjectNode node, String locType,
438 String keyA, String keyB, List<String> values) {
439 try {
440 double valA = Double.parseDouble(values.get(0));
441 double valB = Double.parseDouble(values.get(1));
442 ObjectNode loc = objectNode()
443 .put("type", locType)
444 .put(keyA, valA)
445 .put(keyB, valB);
446 node.set("location", loc);
447
448 } catch (NumberFormatException e) {
449 log.warn("Invalid {} data: long/Y={}, lat/X={}",
450 locType, values.get(0), values.get(1));
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700451 }
452 }
453
Steven Burrows583f4be2016-11-04 14:06:50 +0100454 private void addIps(ObjectNode node, Host h) {
455 Set<IpAddress> ips = h.ipAddresses();
456
457 ArrayNode a = arrayNode();
458 for (IpAddress ip : ips) {
459 a.add(ip.toString());
460 }
461
462 node.set("ips", a);
463 }
464
Simon Hunt6a8cb4f2016-08-09 15:08:57 -0700465 // return list of string values from annotated instance, for given keys
466 // return null if any keys are not present
467 List<String> getAnnotValues(Annotated a, String... annotKeys) {
468 List<String> result = new ArrayList<>(annotKeys.length);
469 for (String k : annotKeys) {
470 String v = a.annotations().value(k);
471 if (v == null) {
472 return null;
473 }
474 result.add(v);
475 }
476 return result;
477 }
478
479 // derive JSON object from annotations
480 private ObjectNode props(Annotations annotations) {
481 ObjectNode p = objectNode();
482 if (annotations != null) {
483 annotations.keys().forEach(k -> p.put(k, annotations.value(k)));
484 }
485 return p;
Simon Huntd5b96732016-07-08 13:22:27 -0700486 }
487
488 private ObjectNode json(UiHost host) {
Steven Burrows583f4be2016-11-04 14:06:50 +0100489 ObjectNode node = objectNode()
Simon Hunt977aa052016-07-20 17:08:29 -0700490 .put("id", host.idAsString())
Simon Hunt98189192016-07-29 19:02:27 -0700491 .put("nodeType", HOST)
Simon Huntd5b96732016-07-08 13:22:27 -0700492 .put("layer", host.layer());
493 // TODO: complete host details
Steven Burrows583f4be2016-11-04 14:06:50 +0100494 Host h = host.backingHost();
495
496 addIps(node, h);
Simon Huntbc30e682017-02-15 18:39:23 -0800497 addProps(node, h);
498 addGeoGridLocation(node, h);
Steven Burrows583f4be2016-11-04 14:06:50 +0100499 addMetaUi(node, host.idAsString());
500
501 return node;
Simon Huntd5b96732016-07-08 13:22:27 -0700502 }
503
Simon Huntc13082f2016-08-03 21:20:23 -0700504 private ObjectNode json(UiSynthLink sLink) {
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800505 return json(sLink.link());
506 }
507
508 private ObjectNode json(UiLink link) {
Simon Hunt3d712522016-08-11 11:20:44 -0700509 ObjectNode data = objectNode()
Simon Hunt8eac4ae2017-01-20 12:56:45 -0800510 .put("id", link.idAsString())
511 .put("epA", link.endPointA())
512 .put("epB", link.endPointB())
513 .put("type", link.type());
514 String pA = link.endPortA();
515 String pB = link.endPortB();
Simon Hunt3d712522016-08-11 11:20:44 -0700516 if (pA != null) {
517 data.put("portA", pA);
518 }
519 if (pB != null) {
520 data.put("portB", pB);
521 }
522 return data;
Simon Huntd5b96732016-07-08 13:22:27 -0700523 }
524
525
Simon Hunt977aa052016-07-20 17:08:29 -0700526 private ObjectNode jsonClosedRegion(UiRegion region) {
Steven Burrows482d9502016-09-27 11:24:58 -0700527 ObjectNode node = objectNode()
Simon Huntb1ce2602016-07-23 14:04:31 -0700528 .put("id", region.idAsString())
Simon Huntf836a872016-08-10 17:37:36 -0700529 .put("name", region.name())
Simon Hunt98189192016-07-29 19:02:27 -0700530 .put("nodeType", REGION)
Steven Burrows19e6e4f2016-10-05 13:27:07 -0500531 .put("nDevs", region.deviceCount())
532 .put("nHosts", region.hostCount());
Simon Hunt53612212016-12-04 17:19:52 -0800533
534 Region r = region.backingRegion();
Simon Huntbc30e682017-02-15 18:39:23 -0800535 addGeoGridLocation(node, r);
Simon Hunt53612212016-12-04 17:19:52 -0800536 addProps(node, r);
Steven Burrows482d9502016-09-27 11:24:58 -0700537
538 addMetaUi(node, region.idAsString());
539 return node;
Simon Hunt977aa052016-07-20 17:08:29 -0700540 }
541
Simon Hunt98189192016-07-29 19:02:27 -0700542 /**
543 * Returns a JSON array representation of a set of regions/devices. Note
544 * that the information is sufficient for showing regions as nodes.
545 *
546 * @param nodes the nodes
547 * @return a JSON representation of the nodes
548 */
549 public ArrayNode closedNodes(Set<UiNode> nodes) {
550 ArrayNode array = arrayNode();
Simon Huntc13082f2016-08-03 21:20:23 -0700551 for (UiNode node : nodes) {
Simon Hunt98189192016-07-29 19:02:27 -0700552 if (node instanceof UiRegion) {
553 array.add(jsonClosedRegion((UiRegion) node));
554 } else if (node instanceof UiDevice) {
555 array.add(json((UiDevice) node));
556 } else {
557 log.warn("Unexpected node instance: {}", node.getClass());
558 }
559 }
560 return array;
561 }
Simon Hunt977aa052016-07-20 17:08:29 -0700562
563 /**
564 * Returns a JSON array representation of a list of regions. Note that the
565 * information about each region is limited to what needs to be used to
566 * show the regions as nodes on the view.
567 *
568 * @param regions the regions
569 * @return a JSON representation of the minimal region information
570 */
571 public ArrayNode closedRegions(Set<UiRegion> regions) {
572 ArrayNode array = arrayNode();
573 for (UiRegion r : regions) {
574 array.add(jsonClosedRegion(r));
575 }
576 return array;
577 }
578
579 /**
580 * Returns a JSON array representation of a list of devices.
581 *
582 * @param devices the devices
583 * @return a JSON representation of the devices
584 */
585 public ArrayNode devices(Set<UiDevice> devices) {
586 ArrayNode array = arrayNode();
587 for (UiDevice device : devices) {
588 array.add(json(device));
589 }
590 return array;
591 }
592
593 /**
594 * Returns a JSON array representation of a list of hosts.
595 *
596 * @param hosts the hosts
597 * @return a JSON representation of the hosts
598 */
599 public ArrayNode hosts(Set<UiHost> hosts) {
600 ArrayNode array = arrayNode();
601 for (UiHost host : hosts) {
602 array.add(json(host));
603 }
604 return array;
605 }
606
Simon Hunt977aa052016-07-20 17:08:29 -0700607 // package-private for unit testing
608 List<Set<UiNode>> splitByLayer(List<String> layerTags,
609 Set<? extends UiNode> nodes) {
610 final int nLayers = layerTags.size();
611 if (!layerTags.get(nLayers - 1).equals(LAYER_DEFAULT)) {
612 throw new IllegalArgumentException(E_DEF_NOT_LAST);
613 }
614
615 List<Set<UiNode>> splitList = new ArrayList<>(layerTags.size());
616 Map<String, Set<UiNode>> byLayer = new HashMap<>(layerTags.size());
617
618 for (String tag : layerTags) {
619 Set<UiNode> set = new HashSet<>();
620 byLayer.put(tag, set);
621 splitList.add(set);
622 }
623
624 for (UiNode n : nodes) {
625 String which = n.layer();
626 if (!layerTags.contains(which)) {
627 which = LAYER_DEFAULT;
628 }
629 byLayer.get(which).add(n);
630 }
631
632 return splitList;
633 }
Steven Burrowse7cc3082016-09-27 11:24:58 -0700634
635 /**
636 * Stores the memento for an element.
637 * This method assumes the payload has an id String, memento ObjectNode
638 *
639 * @param payload event payload
640 */
641 void updateMeta(ObjectNode payload) {
642
643 String id = JsonUtils.string(payload, "id");
644 metaUi.put(id, JsonUtils.node(payload, "memento"));
645
646 log.debug("Storing metadata for {}", id);
647 }
Simon Huntd5b96732016-07-08 13:22:27 -0700648}