blob: 7ee1be7faeb96cb512cf5d1dcf70efa0962f34cb [file] [log] [blame]
jaegonkim6a7b5242018-09-12 23:09:42 +09001/*
2 * Copyright 2018-present Open Networking Foundation
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 */
16package org.onosproject.workflow.api;
17
18
19import com.fasterxml.jackson.core.JsonPointer;
20import com.fasterxml.jackson.core.JsonProcessingException;
21import com.fasterxml.jackson.databind.JsonNode;
22import com.fasterxml.jackson.databind.ObjectMapper;
23import com.fasterxml.jackson.databind.node.ArrayNode;
jaegonkime0f45b52018-10-09 20:23:26 +090024import com.fasterxml.jackson.databind.node.BooleanNode;
25import com.fasterxml.jackson.databind.node.IntNode;
jaegonkim6a7b5242018-09-12 23:09:42 +090026import com.fasterxml.jackson.databind.node.JsonNodeFactory;
27import com.fasterxml.jackson.databind.node.JsonNodeType;
28import com.fasterxml.jackson.databind.node.MissingNode;
jaegonkime0f45b52018-10-09 20:23:26 +090029import com.fasterxml.jackson.databind.node.NumericNode;
jaegonkim6a7b5242018-09-12 23:09:42 +090030import com.fasterxml.jackson.databind.node.ObjectNode;
jaegonkime0f45b52018-10-09 20:23:26 +090031import com.fasterxml.jackson.databind.node.TextNode;
jaegonkim6a7b5242018-09-12 23:09:42 +090032import com.google.common.base.MoreObjects;
jaegonkime0f45b52018-10-09 20:23:26 +090033import org.slf4j.Logger;
34import org.slf4j.LoggerFactory;
jaegonkim6a7b5242018-09-12 23:09:42 +090035
36import java.util.Objects;
37
38/**
39 * Class for json data model tree.
40 */
41public final class JsonDataModelTree implements DataModelTree {
42
jaegonkime0f45b52018-10-09 20:23:26 +090043 private static final Logger log = LoggerFactory.getLogger(JsonDataModelTree.class);
44
jaegonkim6a7b5242018-09-12 23:09:42 +090045 /**
46 * Root node of json data model tree.
47 */
48 private JsonNode root;
49
50 /**
51 * Constructor of JsonDataModelTree.
52 */
53 public JsonDataModelTree() {
54 this.root = JsonNodeFactory.instance.objectNode();
55 }
56
57 /**
58 * Constructor of JsonDataModelTree.
59 * @param root root node of json data model tree
60 */
61 public JsonDataModelTree(JsonNode root) {
62 this.root = root;
63 }
64
65 @Override
66 public DataModelTree subtree(String path) {
67 JsonNode node = root.at(path);
68 if (Objects.isNull(node) || node.isMissingNode()) {
69 return null;
70 }
71 return new JsonDataModelTree(node);
72 }
73
74 @Override
75 public void attach(String path, DataModelTree tree) throws WorkflowException {
jaegonkime0f45b52018-10-09 20:23:26 +090076
jaegonkim6a7b5242018-09-12 23:09:42 +090077 if (root == null || root instanceof MissingNode) {
78 throw new WorkflowException("Invalid root node");
79 }
80
81 JsonPointer ptr = JsonPointer.compile(path);
jaegonkim6a7b5242018-09-12 23:09:42 +090082
83 if (!(tree instanceof JsonDataModelTree)) {
84 throw new WorkflowException("Invalid subTree(" + tree + ")");
85 }
86 JsonNode attachingNode = ((JsonDataModelTree) tree).root();
87
jaegonkime0f45b52018-10-09 20:23:26 +090088 attach(ptr, attachingNode);
89 }
jaegonkim6a7b5242018-09-12 23:09:42 +090090
jaegonkime0f45b52018-10-09 20:23:26 +090091 private void attach(JsonPointer ptr, JsonNode attachingNode) throws WorkflowException {
92
93 JsonNode node = root.at(ptr);
94 if (!(node instanceof MissingNode)) {
95 throw new WorkflowException("Path(" + ptr + ") has already subtree(" + node + ")");
jaegonkim6a7b5242018-09-12 23:09:42 +090096 }
97
jaegonkime0f45b52018-10-09 20:23:26 +090098 if (ptr.last().getMatchingIndex() != -1) {
99
100 alloc(ptr.head(), Nodetype.ARRAY);
101 JsonNode parentNode = root.at(ptr.head());
102 if (!parentNode.isArray()) {
103 throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
104 }
105 int index = ptr.last().getMatchingIndex();
106 ((ArrayNode) parentNode).insert(index, attachingNode);
107
108 } else if (ptr.last().getMatchingProperty() != null) {
109
110 alloc(ptr.head(), Nodetype.MAP);
111 JsonNode parentNode = root.at(ptr.head());
112 if (!parentNode.isObject()) {
113 throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
114 }
115 String key = ptr.last().getMatchingProperty();
116 ((ObjectNode) parentNode).put(key, attachingNode);
117
118 } else {
119 throw new WorkflowException("Invalid path(" + ptr + ")");
120 }
121 }
122
123 @Override
124 public void remove(String path) throws WorkflowException {
125 JsonPointer ptr = JsonPointer.compile(path);
126 remove(ptr);
127 }
128
129 private void remove(JsonPointer ptr) throws WorkflowException {
130
131 JsonNode node = root.at(ptr);
132 if (node instanceof MissingNode) {
133 log.warn("{} does not have valid node", ptr);
134 return;
135 }
136
137 if (ptr.last().getMatchingIndex() != -1) {
138
139 JsonNode parentNode = root.at(ptr.head());
140 if (!parentNode.isArray()) {
141 throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Array)");
142 }
143 int index = ptr.last().getMatchingIndex();
144 ((ArrayNode) parentNode).remove(index);
145
146 } else if (ptr.last().getMatchingProperty() != null) {
147
148 JsonNode parentNode = root.at(ptr.head());
149 if (!parentNode.isObject()) {
150 throw new WorkflowException("Invalid parentNode type(" + parentNode.getNodeType() + " != Object)");
151 }
152 String key = ptr.last().getMatchingProperty();
153 ((ObjectNode) parentNode).remove(key);
154
155 } else {
156 throw new WorkflowException("Invalid path(" + ptr + ")");
157 }
jaegonkim6a7b5242018-09-12 23:09:42 +0900158 }
159
160 @Override
161 public JsonDataModelTree alloc(String path, Nodetype leaftype) throws WorkflowException {
162 if (root == null || root instanceof MissingNode) {
163 throw new WorkflowException("Invalid root node");
164 }
165
166 JsonPointer ptr = JsonPointer.compile(path);
167 return alloc(ptr, leaftype);
168 }
169
170 /**
171 * Allocates json data model tree on json pointer path with specific leaf type.
172 * @param ptr json pointer to allocate
173 * @param leaftype type of leaf node
174 * @return json data model tree
175 * @throws WorkflowException workflow exception
176 */
177 private JsonDataModelTree alloc(JsonPointer ptr, Nodetype leaftype) throws WorkflowException {
178 if (root == null || root instanceof MissingNode) {
179 throw new WorkflowException("Invalid root node");
180 }
181
182 switch (leaftype) {
183 case MAP:
184 alloc(root, ptr, JsonNodeType.OBJECT);
185 break;
186 case ARRAY:
187 alloc(root, ptr, JsonNodeType.ARRAY);
188 break;
189 default:
190 throw new WorkflowException("Not supported leaftype(" + leaftype + ")");
191 }
192 return this;
193 }
194
195 /**
196 * Gets root json node.
197 * @return root json node
198 * @throws WorkflowException workflow exception
199 */
200 public JsonNode root() throws WorkflowException {
201 return nodeAt("");
202 }
203
204 /**
205 * Gets root json node as ObjectNode (MAP type).
206 * @return root json node as ObjectNode
207 * @throws WorkflowException workflow exception
208 */
209 public ObjectNode rootObject() throws WorkflowException {
210 return objectAt("");
211 }
212
213 /**
214 * Gets root json node as ArrayNode (Array type).
215 * @return root json node as ArrayNode
216 * @throws WorkflowException workflow exception
217 */
218 public ArrayNode rootArray() throws WorkflowException {
219 return arrayAt("");
220 }
221
222 /**
223 * Gets json node on specific path.
224 * @param path path of json node
225 * @return json node on specific path
226 * @throws WorkflowException workflow exception
227 */
228 public JsonNode nodeAt(String path) throws WorkflowException {
229 JsonPointer ptr = JsonPointer.compile(path);
230 return nodeAt(ptr);
231 }
232
233 /**
234 * Gets json node on specific json pointer.
235 * @param ptr json pointer
236 * @return json node on specific json pointer.
237 * @throws WorkflowException workflow exception
238 */
239 public JsonNode nodeAt(JsonPointer ptr) throws WorkflowException {
240 if (root == null || root instanceof MissingNode) {
241 throw new WorkflowException("Invalid root node");
242 }
243 JsonNode node = root.at(ptr);
244 return node;
245 }
246
247 /**
248 * Gets json node on specific path as ObjectNode.
249 * @param path path of json node
250 * @return ObjectNode type json node on specific path
251 * @throws WorkflowException workflow exception
252 */
253 public ObjectNode objectAt(String path) throws WorkflowException {
254 JsonPointer ptr = JsonPointer.compile(path);
255 return objectAt(ptr);
256 }
257
258 /**
259 * Gets json node on specific json pointer as ObjectNode.
260 * @param ptr json pointer
261 * @return ObjectNode type json node on specific json pointer.
262 * @throws WorkflowException workflow exception
263 */
264 public ObjectNode objectAt(JsonPointer ptr) throws WorkflowException {
265 if (root == null || root instanceof MissingNode) {
266 throw new WorkflowException("Invalid root node");
267 }
268 JsonNode node = root.at(ptr);
jaegonkime0f45b52018-10-09 20:23:26 +0900269 if (node instanceof MissingNode) {
270 return null;
271 }
jaegonkim6a7b5242018-09-12 23:09:42 +0900272 if (!(node instanceof ObjectNode)) {
273 throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
274 }
275 return (ObjectNode) node;
276 }
277
278 /**
279 * Gets json node on specific path as ArrayNode.
280 * @param path path of json node
281 * @return ArrayNode type json node on specific path
282 * @throws WorkflowException workflow exception
283 */
284 public ArrayNode arrayAt(String path) throws WorkflowException {
285 JsonPointer ptr = JsonPointer.compile(path);
286 return arrayAt(ptr);
287 }
288
289 /**
290 * Gets json node on specific json pointer as ArrayNode.
291 * @param ptr json pointer
292 * @return ArrayNode type json node on specific json pointer.
293 * @throws WorkflowException workflow exception
294 */
295 public ArrayNode arrayAt(JsonPointer ptr) throws WorkflowException {
296 if (root == null || root instanceof MissingNode) {
297 throw new WorkflowException("Invalid root node");
298 }
299 JsonNode node = root.at(ptr);
jaegonkime0f45b52018-10-09 20:23:26 +0900300 if (node instanceof MissingNode) {
301 return null;
302 }
jaegonkim6a7b5242018-09-12 23:09:42 +0900303 if (!(node instanceof ArrayNode)) {
304 throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
305 }
306 return (ArrayNode) node;
307 }
308
309 /**
jaegonkime0f45b52018-10-09 20:23:26 +0900310 * Gets text node on specific path.
311 * @param path path of json node
312 * @return text on specific path
313 * @throws WorkflowException workflow exception
314 */
315 public String textAt(String path) throws WorkflowException {
316 JsonPointer ptr = JsonPointer.compile(path);
317 return textAt(ptr);
318 }
319
320 /**
321 * Gets text on specific json pointer.
322 * @param ptr json pointer
323 * @return text on specific json pointer
324 * @throws WorkflowException workflow exception
325 */
326 public String textAt(JsonPointer ptr) throws WorkflowException {
327 if (root == null || root instanceof MissingNode) {
328 throw new WorkflowException("Invalid root node");
329 }
330 JsonNode node = root.at(ptr);
331 if (node instanceof MissingNode) {
332 return null;
333 }
334 if (!(node instanceof TextNode)) {
335 throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
336 }
337 return ((TextNode) node).asText();
338 }
339
340 /**
341 * Gets integer node on specific path.
342 * @param path path of json node
343 * @return integer on specific path
344 * @throws WorkflowException workflow exception
345 */
346 public Integer intAt(String path) throws WorkflowException {
347 JsonPointer ptr = JsonPointer.compile(path);
348 return intAt(ptr);
349 }
350
351 /**
352 * Gets integer on specific json pointer.
353 * @param ptr json pointer
354 * @return integer on specific json pointer
355 * @throws WorkflowException workflow exception
356 */
357 public Integer intAt(JsonPointer ptr) throws WorkflowException {
358 if (root == null || root instanceof MissingNode) {
359 throw new WorkflowException("Invalid root node");
360 }
361 JsonNode node = root.at(ptr);
362 if (node instanceof MissingNode) {
363 return null;
364 }
365 if (!(node instanceof NumericNode)) {
366 throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
367 }
368 return ((NumericNode) node).asInt();
369 }
370
371 /**
372 * Gets boolean on specific path.
373 * @param path path of json node
374 * @return boolean on specific path
375 * @throws WorkflowException workflow exception
376 */
377 public Boolean booleanAt(String path) throws WorkflowException {
378 JsonPointer ptr = JsonPointer.compile(path);
379 return booleanAt(ptr);
380 }
381
382 /**
383 * Gets boolean on specific json pointer.
384 * @param ptr json pointer
385 * @return boolean on specific json pointer
386 * @throws WorkflowException workflow exception
387 */
388 public Boolean booleanAt(JsonPointer ptr) throws WorkflowException {
389 if (root == null || root instanceof MissingNode) {
390 throw new WorkflowException("Invalid root node");
391 }
392 JsonNode node = root.at(ptr);
393 if (node instanceof MissingNode) {
394 return null;
395 }
396 if (!(node instanceof BooleanNode)) {
397 throw new WorkflowException("Invalid node(" + node + ") at " + ptr);
398 }
399 return ((BooleanNode) node).asBoolean();
400 }
401
402 /**
403 * Sets text on specific json path.
404 * @param path json path
405 * @param text text to set
406 * @throws WorkflowException workflow exception
407 */
408 public void setAt(String path, String text) throws WorkflowException {
409 JsonPointer ptr = JsonPointer.compile(path);
410 setAt(ptr, text);
411 }
412
413 /**
414 * Sets text on the specific json pointer.
415 * @param ptr json pointer
416 * @param text text to set
417 * @throws WorkflowException workflow exception
418 */
419 public void setAt(JsonPointer ptr, String text) throws WorkflowException {
420 TextNode textNode = TextNode.valueOf(text);
421 attach(ptr, textNode);
422 }
423
424 /**
425 * Sets boolean on specific json path.
426 * @param path json path
427 * @param isTrue boolean to set
428 * @throws WorkflowException workflow exception
429 */
430 public void setAt(String path, Boolean isTrue) throws WorkflowException {
431 JsonPointer ptr = JsonPointer.compile(path);
432 setAt(ptr, isTrue);
433 }
434
435 /**
436 * Sets boolean on the specific json pointer.
437 * @param ptr json pointer
438 * @param isTrue boolean to set
439 * @throws WorkflowException workflow exception
440 */
441 public void setAt(JsonPointer ptr, Boolean isTrue) throws WorkflowException {
442 BooleanNode booleanNode = BooleanNode.valueOf(isTrue);
443 attach(ptr, booleanNode);
444 }
445
446 /**
447 * Sets integer on specific json path.
448 * @param path json path
449 * @param number number to set
450 * @throws WorkflowException workflow exception
451 */
452 public void setAt(String path, Integer number) throws WorkflowException {
453 JsonPointer ptr = JsonPointer.compile(path);
454 setAt(ptr, number);
455 }
456
457 /**
458 * Sets integer on the specific json pointer.
459 * @param ptr json pointer
460 * @param number number to set
461 * @throws WorkflowException workflow exception
462 */
463 public void setAt(JsonPointer ptr, Integer number) throws WorkflowException {
464 IntNode intNode = IntNode.valueOf(number);
465 attach(ptr, intNode);
466 }
467
468 /**
jaegonkim6a7b5242018-09-12 23:09:42 +0900469 * Allocates json data model tree on json pointer path with specific leaf type.
470 * @param node current json node in the json tree path
471 * @param ptr json pointer
472 * @param leaftype leaf type to be allocated
473 * @return allocated json node
474 * @throws WorkflowException workflow exception
475 */
476 private JsonNode alloc(JsonNode node, JsonPointer ptr, JsonNodeType leaftype) throws WorkflowException {
477
478 if (ptr.matches()) {
jaegonkime0f45b52018-10-09 20:23:26 +0900479 if (node == null || node instanceof MissingNode) {
jaegonkim6a7b5242018-09-12 23:09:42 +0900480 node = createEmpty(leaftype);
481 } else {
482 //TODO: checking existing node type is matched with leaftype
jaegonkime0f45b52018-10-09 20:23:26 +0900483 if (!Objects.equals(node.getNodeType(), leaftype)) {
jaegonkim6a7b5242018-09-12 23:09:42 +0900484 throw new WorkflowException("Requesting leaftype(" + leaftype + ") is not matched with "
485 + "existing nodetype(" + node.getNodeType() + ") for " + ptr);
486 }
487 }
488 return node;
489 }
490
491 if (ptr.getMatchingIndex() != -1) {
jaegonkime0f45b52018-10-09 20:23:26 +0900492 if (node == null || node instanceof MissingNode) {
jaegonkim6a7b5242018-09-12 23:09:42 +0900493 node = createEmpty(JsonNodeType.ARRAY);
494 }
495 JsonNode child = alloc(node.get(ptr.getMatchingIndex()), ptr.tail(), leaftype);
496 if (!node.has(ptr.getMatchingIndex())) {
497 ((ArrayNode) node).insert(ptr.getMatchingIndex(), child);
498 }
499 } else if (ptr.getMatchingProperty() != null) {
jaegonkime0f45b52018-10-09 20:23:26 +0900500 if (node == null || node instanceof MissingNode) {
jaegonkim6a7b5242018-09-12 23:09:42 +0900501 node = createEmpty(JsonNodeType.OBJECT);
502 }
503 JsonNode child = alloc(node.get(ptr.getMatchingProperty()), ptr.tail(), leaftype);
504 if (!node.has(ptr.getMatchingProperty())) {
505 ((ObjectNode) node).put(ptr.getMatchingProperty(), child);
506 }
507 }
508 return node;
509 }
510
511 /**
512 * Creating empty json node.
513 * @param type json node type to create
514 * @return created json node
515 * @throws WorkflowException workflow exception
516 */
517 private JsonNode createEmpty(JsonNodeType type) throws WorkflowException {
518 if (type == JsonNodeType.OBJECT) {
519 return JsonNodeFactory.instance.objectNode();
520 } else if (type == JsonNodeType.ARRAY) {
521 return JsonNodeFactory.instance.arrayNode();
522 } else if (type == JsonNodeType.STRING) {
523 return JsonNodeFactory.instance.textNode("");
524 } else {
525 throw new WorkflowException("Not supported JsonNodetype(" + type + ")");
526 }
527 }
528
529 /**
530 * Gets the pretty json formatted string of this json data model tree.
531 * @return pretty json formatted string of this json data model tree
532 */
533 public String formattedRootString() {
534 String str = "";
535 try {
536 str = (new ObjectMapper()).writerWithDefaultPrettyPrinter().writeValueAsString(root);
537 } catch (JsonProcessingException e) {
jaegonkime0f45b52018-10-09 20:23:26 +0900538 log.error("Exception: ", e);
jaegonkim6a7b5242018-09-12 23:09:42 +0900539 }
540 return str;
541 }
542
543 @Override
544 public String toString() {
545 return MoreObjects.toStringHelper(getClass())
jaegonkime0f45b52018-10-09 20:23:26 +0900546 .add("json", root)
jaegonkim6a7b5242018-09-12 23:09:42 +0900547 .toString();
548 }
549}
550