blob: 87a3d515e1d42834eb6cf478b448c84462fa4dce [file] [log] [blame]
chengfanc58d4be2016-09-20 10:33:12 +08001/*
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.protocol.restconf.server.utils.parser.json;
18
19import com.fasterxml.jackson.databind.node.ObjectNode;
20import com.google.common.base.Splitter;
21import com.google.common.collect.Lists;
22import org.onosproject.protocol.restconf.server.utils.exceptions.JsonParseException;
23import org.onosproject.protocol.restconf.server.utils.parser.api.JsonBuilder;
chengfan7b2a60b2016-10-08 20:27:15 +080024import org.onosproject.protocol.restconf.server.utils.parser.api.NormalizedYangNode;
Henry Yu52a8a722016-12-19 14:50:50 -050025import org.onosproject.yms.app.ydt.YdtExtendedContext;
26import org.onosproject.yms.app.ydt.YdtSingleInstanceLeafNode;
chengfanc58d4be2016-09-20 10:33:12 +080027import org.onosproject.yms.ydt.YdtBuilder;
28import org.onosproject.yms.ydt.YdtContext;
29import org.onosproject.yms.ydt.YdtContextOperationType;
30import org.onosproject.yms.ydt.YdtListener;
31import org.onosproject.yms.ydt.YdtType;
32import org.onosproject.yms.ydt.YdtWalker;
33
34import java.io.UnsupportedEncodingException;
35import java.net.URLDecoder;
36import java.util.ArrayList;
37import java.util.List;
38
39import static com.google.common.base.Preconditions.checkNotNull;
40import static org.onosproject.yms.ydt.YdtContextOperationType.NONE;
41
42/**
43 * Utils to complete the conversion between JSON and YDT(YANG DATA MODEL).
44 */
45public final class ParserUtils {
46
47 private static final Splitter SLASH_SPLITTER = Splitter.on('/');
48 private static final Splitter COMMA_SPLITTER = Splitter.on(',');
49 private static final String EQUAL = "=";
50 private static final String COMMA = ",";
51 private static final String COLON = ":";
Henry Yu52a8a722016-12-19 14:50:50 -050052 private static final String SLASH = "/";
chengfanc58d4be2016-09-20 10:33:12 +080053 private static final String URI_ENCODING_CHAR_SET = "ISO-8859-1";
54 private static final String ERROR_LIST_MSG = "List/Leaf-list node should be " +
55 "in format \"nodeName=key\"or \"nodeName=instance-value\"";
56 private static final String ERROR_MODULE_MSG = "First node should be in " +
57 "format \"moduleName:nodeName\"";
58
59 // no instantiation
60 private ParserUtils() {
61 }
62
63 /**
64 * Converts URI identifier to YDT builder.
65 *
66 * @param id the uri identifier from web request
67 * @param builder the base ydt builder
68 * @param opType the ydt operation type for the uri
69 */
70 public static void convertUriToYdt(String id,
71 YdtBuilder builder,
72 YdtContextOperationType opType) {
73 checkNotNull(id, "uri identifier should not be null");
74 List<String> paths = urlPathArgsDecode(SLASH_SPLITTER.split(id));
75 if (!paths.isEmpty()) {
Henry Yu7dbfc202016-10-24 16:00:37 -040076 processPathSegments(paths, builder, opType, true);
chengfanc58d4be2016-09-20 10:33:12 +080077 }
78 }
79
80 /**
81 * Converts JSON objectNode to YDT builder. The objectNode can be any
82 * standard JSON node, node just for RESTconf payload.
83 *
84 * @param objectNode the objectNode from web request
85 * @param builder the base ydt builder
86 */
87 public static void convertJsonToYdt(ObjectNode objectNode,
88 YdtBuilder builder) {
89
90 JsonToYdtListener listener = new JsonToYdtListener(builder);
91 new DefaultJsonWalker().walk(listener, null, objectNode);
92 }
93
94 /**
95 * Converts a Ydt context tree to a JSON object.
96 *
97 * @param rootName the name of the YdtContext from which the YdtListener
98 * start to builder a Json Object
99 * @param context a abstract data model for YANG data
100 * @param walker abstraction of an entity which provides interfaces for
101 * YDT walk
102 * @return the JSON node corresponding the YANG data
103 */
104 public static ObjectNode convertYdtToJson(String rootName,
105 YdtContext context,
106 YdtWalker walker) {
107 JsonBuilder builder = new DefaultJsonBuilder();
108 YdtListener listener = new YdtToJsonListener(rootName, builder);
109 walker.walk(listener, context);
110 return builder.getTreeNode();
111 }
112
113 /**
114 * Converts a list of path segments to a YDT builder tree.
115 *
Henry Yu7dbfc202016-10-24 16:00:37 -0400116 * @param paths the list of path segments split from URI
117 * @param builder the base YDT builder
118 * @param opType the YDT operation type for the Path segment
119 * @param isFirstIteration true if paths contains all the URI segments
chengfanc58d4be2016-09-20 10:33:12 +0800120 * @return the YDT builder with the tree info of paths
121 */
122 private static YdtBuilder processPathSegments(List<String> paths,
123 YdtBuilder builder,
Henry Yu7dbfc202016-10-24 16:00:37 -0400124 YdtContextOperationType opType,
125 boolean isFirstIteration) {
chengfanc58d4be2016-09-20 10:33:12 +0800126 if (paths.isEmpty()) {
127 return builder;
128 }
chengfanc58d4be2016-09-20 10:33:12 +0800129
Henry Yu7dbfc202016-10-24 16:00:37 -0400130 boolean isLastSegment = paths.size() == 1;
chengfanc58d4be2016-09-20 10:33:12 +0800131
Henry Yu7dbfc202016-10-24 16:00:37 -0400132 /*
133 * Process the first segment in path.
134 *
135 * BUG ONOS-5500: YMS requires the treatment for the very first
136 * segment in the URI path to be different than the rest. So,
137 * we added a parameter, isFirstIteration, to this function.
138 * It is set to true by the caller when this function is called
139 * the very first time (i.e,, "paths" contains all the segments).
140 *
141 */
142 YdtContextOperationType opTypeForThisSegment = isLastSegment ? opType : NONE;
143 String segment = paths.iterator().next();
144 processSinglePathSegment(segment, builder, opTypeForThisSegment, isFirstIteration);
145
146 if (isLastSegment) {
147 // We have hit the base case of recursion.
chengfanc58d4be2016-09-20 10:33:12 +0800148 return builder;
149 }
Henry Yu7dbfc202016-10-24 16:00:37 -0400150
151 /*
152 * Chop off the first segment, and recursively process the rest
153 * of the path segments.
154 */
chengfanc58d4be2016-09-20 10:33:12 +0800155 List<String> remainPaths = paths.subList(1, paths.size());
Henry Yu7dbfc202016-10-24 16:00:37 -0400156 processPathSegments(remainPaths, builder, opType, false);
chengfanc58d4be2016-09-20 10:33:12 +0800157
158 return builder;
159 }
160
Henry Yu7dbfc202016-10-24 16:00:37 -0400161 private static void processSinglePathSegment(String pathSegment,
162 YdtBuilder builder,
163 YdtContextOperationType opType,
164 boolean isTopLevelSegment) {
165 if (pathSegment.contains(COLON)) {
166 processPathSegmentWithNamespace(pathSegment, builder, opType, isTopLevelSegment);
167 } else {
168 processPathSegmentWithoutNamespace(pathSegment, builder, opType);
169 }
170 }
171
172 private static void processPathSegmentWithNamespace(String pathSegment,
173 YdtBuilder builder,
174 YdtContextOperationType opType,
175 boolean isTopLevelSegment) {
176 if (isTopLevelSegment) {
177 /*
178 * BUG ONOS-5500: If this segment refers to the first node in
179 * the path (i.e., top level of the model hierarchy), then
180 * YMS requires 2 YDT nodes to be added instead of one. The
181 * first one contains the namespace, and the second contains
182 * the node name. For other segments in the path, only one
183 * YDT node is needed.
184 */
185 addModule(builder, pathSegment);
186 }
187
188 String nodeName = getLatterSegment(pathSegment, COLON);
189 String namespace = getPreSegment(pathSegment, COLON);
190 convertPathSegmentToYdtNode(nodeName, namespace, builder, opType);
191 }
192
193 private static void processPathSegmentWithoutNamespace(String pathSegment,
194 YdtBuilder builder,
195 YdtContextOperationType opType) {
196 convertPathSegmentToYdtNode(pathSegment, null, builder, opType);
197 }
198
199 private static void convertPathSegmentToYdtNode(String pathSegment,
200 String namespace,
201 YdtBuilder builder,
202 YdtContextOperationType opType) {
203 if (pathSegment.contains(EQUAL)) {
204 addListOrLeafList(pathSegment, namespace, builder, opType);
205 } else {
206 addLeaf(pathSegment, namespace, builder, opType);
207 }
208 }
209
chengfanc58d4be2016-09-20 10:33:12 +0800210 private static YdtBuilder addListOrLeafList(String path,
Henry Yu7dbfc202016-10-24 16:00:37 -0400211 String namespace,
chengfanc58d4be2016-09-20 10:33:12 +0800212 YdtBuilder builder,
213 YdtContextOperationType opType) {
214 String nodeName = getPreSegment(path, EQUAL);
215 String keyStr = getLatterSegment(path, EQUAL);
216 if (keyStr == null) {
217 throw new JsonParseException(ERROR_LIST_MSG);
218 }
sonu gupta1bb37b82016-11-11 16:51:18 +0530219
chengfanc58d4be2016-09-20 10:33:12 +0800220 if (keyStr.contains(COMMA)) {
221 List<String> keys = Lists.
222 newArrayList(COMMA_SPLITTER.split(keyStr));
sonu gupta1bb37b82016-11-11 16:51:18 +0530223 builder.addMultiInstanceChild(nodeName, namespace, keys, opType);
chengfanc58d4be2016-09-20 10:33:12 +0800224 } else {
Henry Yu7dbfc202016-10-24 16:00:37 -0400225 builder.addMultiInstanceChild(nodeName, namespace,
sonu gupta1bb37b82016-11-11 16:51:18 +0530226 Lists.newArrayList(keyStr), opType);
chengfanc58d4be2016-09-20 10:33:12 +0800227 }
228 return builder;
229 }
230
Henry Yu7dbfc202016-10-24 16:00:37 -0400231 private static YdtBuilder addLeaf(String path,
232 String namespace,
233 YdtBuilder builder,
chengfanc58d4be2016-09-20 10:33:12 +0800234 YdtContextOperationType opType) {
235 checkNotNull(path);
Henry Yu7dbfc202016-10-24 16:00:37 -0400236 builder.addChild(path, namespace, opType);
chengfanc58d4be2016-09-20 10:33:12 +0800237 return builder;
238 }
239
chengfan7b2a60b2016-10-08 20:27:15 +0800240 private static YdtBuilder addModule(YdtBuilder builder, String path) {
241 String moduleName = getPreSegment(path, COLON);
242 if (moduleName == null) {
243 throw new JsonParseException(ERROR_MODULE_MSG);
244 }
245 builder.addChild(moduleName, null, YdtType.SINGLE_INSTANCE_NODE);
246 return builder;
247 }
248
249 private static YdtBuilder addNode(String path, YdtBuilder builder,
250 YdtContextOperationType opType) {
251 String nodeName = getLatterSegment(path, COLON);
252 builder.addChild(nodeName,
253 null,
254 YdtType.SINGLE_INSTANCE_NODE,
255 opType);
256 return builder;
257 }
258
chengfanc58d4be2016-09-20 10:33:12 +0800259 /**
260 * Returns the previous segment of a path which is separated by a split char.
261 * For example:
262 * <pre>
263 * "foo:bar", ":" --> "foo"
264 * </pre>
265 *
266 * @param path the original path string
267 * @param splitChar char used to split the path
268 * @return the previous segment of the path
269 */
270 private static String getPreSegment(String path, String splitChar) {
271 int idx = path.indexOf(splitChar);
272 if (idx == -1) {
273 return null;
274 }
275
276 if (path.indexOf(splitChar, idx + 1) != -1) {
277 return null;
278 }
279
280 return path.substring(0, idx);
281 }
282
283 /**
284 * Returns the latter segment of a path which is separated by a split char.
285 * For example:
286 * <pre>
287 * "foo:bar", ":" --> "bar"
288 * </pre>
289 *
290 * @param path the original path string
291 * @param splitChar char used to split the path
292 * @return the latter segment of the path
293 */
294 private static String getLatterSegment(String path, String splitChar) {
295 int idx = path.indexOf(splitChar);
296 if (idx == -1) {
297 return path;
298 }
299
300 if (path.indexOf(splitChar, idx + 1) != -1) {
301 return null;
302 }
303
304 return path.substring(idx + 1);
305 }
306
307 /**
308 * Converts a list of path from the original format to ISO-8859-1 code.
309 *
310 * @param paths the original paths
311 * @return list of decoded paths
312 */
313 public static List<String> urlPathArgsDecode(Iterable<String> paths) {
314 try {
315 List<String> decodedPathArgs = new ArrayList<>();
316 for (String pathArg : paths) {
317 String decode = URLDecoder.decode(pathArg,
318 URI_ENCODING_CHAR_SET);
319 decodedPathArgs.add(decode);
320 }
321 return decodedPathArgs;
322 } catch (UnsupportedEncodingException e) {
323 throw new JsonParseException("Invalid URL path arg '" +
324 paths + "': ", e);
325 }
326 }
chengfan7b2a60b2016-10-08 20:27:15 +0800327
328 /**
329 * Converts a field to a simple YANG node description which contains the
330 * namespace and name information.
331 *
332 * @param field field name of a JSON body, or a segment of a URI
333 * in a request of RESTCONF
334 * @return a simple normalized YANG node
335 */
336 public static NormalizedYangNode buildNormalizedNode(String field) {
337 String namespace = getPreSegment(field, COLON);
338 String name = getLatterSegment(field, COLON);
339 return new NormalizedYangNode(namespace, name);
340 }
Henry Yu528007c2016-11-01 15:49:59 -0400341
342
343 /**
344 * Extracts the node name from a YDT node and encodes it in JSON format.
345 * A JSON encoded node name has the following format:
346 * <p>
347 * module_name ":" node_name
348 * <p>
349 * where module_name is name of the YANG module in which the data
350 * resource is defined, and node_name is the name of the data resource.
351 * <p>
352 * If the YDT node is null or its node name field is null, then the function
353 * returns null. If the node name field is not null but module name field is,
354 * then the function returns only the node name.
355 *
356 * @param ydtContext YDT node of the target data resource
357 * @return JSON encoded name of the target data resource
358 */
359 public static String getJsonNameFromYdtNode(YdtContext ydtContext) {
360 if (ydtContext == null) {
361 return null;
362 }
363
364 String nodeName = ydtContext.getName();
365 if (nodeName == null) {
366 return null;
367 }
368
369 /*
370 * The namespace field in YDT node is a string which contains a list
371 * of identifiers separated by colon (:). e.g.,
372 *
373 * {identifier ":" identifier}+
374 *
375 * The last identifier in the string is the YANG module name.
376 */
377 String moduleName = getModuleNameFromNamespace(ydtContext.getNamespace());
378 if (moduleName == null) {
379 return nodeName;
380 } else {
381 return moduleName + COLON + nodeName;
382 }
383 }
384
385 private static String getModuleNameFromNamespace(String namespace) {
386 if (namespace == null) {
387 return null;
388 }
389
390 String moduleName = null;
391
392 if (namespace.contains(COLON)) {
393 String[] tokens = namespace.split(COLON);
394 moduleName = tokens[tokens.length - 1];
395 }
396
397 return moduleName;
398 }
Henry Yu52a8a722016-12-19 14:50:50 -0500399
400 /**
401 * Extracts the URI from the given YANG Data Tree (YDT). The URI is
402 * presented in string format. If no URI is found in the YDT, an
403 * empty string is returned.
404 *
405 * @param ydtBuilder the YDT from which the URI is extracted
406 * @return URI
407 */
408 public static String getUriInCompositeYdt(YdtBuilder ydtBuilder) {
409 checkNotNull(ydtBuilder, "ydt cannot be null");
410
411 StringBuilder uriBuilder = new StringBuilder();
412 YdtContext ydtNode = ydtBuilder.getRootNode().getFirstChild();
413 String currModuleName = null;
414
415 boolean isLastNodeInUri = false;
416 int levelNum = 0;
417 while (((YdtExtendedContext) ydtNode).getYdtContextOperationType() == NONE ||
418 isLastNodeInUri) {
419 currModuleName = addNodeToUri(ydtNode, currModuleName,
420 levelNum, uriBuilder);
421
422 if (ydtNode.getYdtType() == YdtType.MULTI_INSTANCE_NODE) {
423 if (isLastNodeInUri) {
424 addKeyNodeToUri(ydtNode, uriBuilder);
425 break;
426 }
427
428 YdtContext firstChild = ydtNode.getFirstChild();
429 YdtContext lastChild = ydtNode.getLastChild();
430 if ((firstChild.getYdtType() == YdtType.SINGLE_INSTANCE_LEAF_VALUE_NODE) &&
431 ((YdtSingleInstanceLeafNode) firstChild).isKeyLeaf()) {
432 currModuleName = addNodeToUri(firstChild,
433 currModuleName,
434 levelNum,
435 uriBuilder);
436 ydtNode = lastChild;
437 } else {
438 currModuleName = addNodeToUri(lastChild,
439 currModuleName,
440 levelNum,
441 uriBuilder);
442 ydtNode = firstChild;
443 }
444 } else {
445 ydtNode = ydtNode.getFirstChild();
446 }
447
448 if (isLastNodeInUri) {
449 break;
450 }
451
452 if (((YdtExtendedContext) ydtNode).getYdtContextOperationType() != NONE) {
453 isLastNodeInUri = true;
454 }
455
456 levelNum++;
457 }
458
459 return uriBuilder.toString();
460 }
461
462 /**
463 * Finds the key leaf node from the given multi-instance YDT node and
464 * appends the key value to the given URI string.
465 * <p>
466 * If no key leaf node is found, then the given URI is unchanged.
467 *
468 * @param ydtNode YDT node under which the key leaf node is found
469 * @param uriBuilder URI
470 */
471 private static void addKeyNodeToUri(YdtContext ydtNode,
472 StringBuilder uriBuilder) {
473 YdtContext child = ydtNode.getFirstChild();
474
475 while (child != null) {
476 if (child.getYdtType() == YdtType.SINGLE_INSTANCE_LEAF_VALUE_NODE) {
477 if (((YdtSingleInstanceLeafNode) child).isKeyLeaf()) {
478 uriBuilder.append(EQUAL).append(ydtNode.getValue());
479 break;
480 }
481 }
482 child = child.getNextSibling();
483 }
484 }
485
486 /**
487 * Extracts the path segment from a given YANG Data Tree (YDT) node and
488 * appends it to the given URI string.
489 *
490 * @param ydtNode YDT node from which the URI segment is extracted
491 * @param currModuleName current YANG module name in URI
492 * @param ydtNodeDepth depth of the YDT node's position in the tree
493 * @param uriBuilder URI to which the URI segment appends
494 * @return YANG module name extracted from the YDT node
495 */
496 private static String addNodeToUri(YdtContext ydtNode,
497 String currModuleName,
498 int ydtNodeDepth,
499 StringBuilder uriBuilder) {
500 YdtType nodeType = ydtNode.getYdtType();
501
502 /*
503 * The given YDT node is the root of the YDT. Only the module name
504 * needs to be extracted and added to URI.
505 */
506 if (ydtNodeDepth == 0) {
507 String moduleName = ydtNode.getModuleNameAsNameSpace();
508 uriBuilder.append(moduleName);
509 return moduleName;
510 }
511
512 if (ydtNodeDepth == 1) {
513 String moduleName = ydtNode.getModuleNameAsNameSpace();
514 uriBuilder.append(COLON);
515 uriBuilder.append(ydtNode.getName());
516 return moduleName;
517 }
518
519 if (nodeType == YdtType.SINGLE_INSTANCE_LEAF_VALUE_NODE &&
520 ((YdtSingleInstanceLeafNode) ydtNode).isKeyLeaf()) {
521 uriBuilder.append(EQUAL).append(ydtNode.getValue());
522 return currModuleName;
523 } else {
524 uriBuilder.append(SLASH);
525 }
526
527 String moduleName = ydtNode.getModuleNameAsNameSpace();
528
529 if (currModuleName == null || !currModuleName.equals(moduleName)) {
530 uriBuilder.append(moduleName).append(COLON);
531 }
532
533 uriBuilder.append(ydtNode.getName());
534
535 return moduleName;
536 }
537
538 /**
539 * Retrieves the top data node of the subtree from the given composite
540 * YANG Data Tree (YDT) which contains both the URI path and the data
541 * subtree to which the URI points.
542 * <p>
543 * A null is returned if no data subtree is found.
544 *
545 * @param ydtBuilder the given YDT
546 * @return the top data node of the data subtree.
547 */
548 public static YdtContext findTopNodeInCompositeYdt(YdtBuilder ydtBuilder) {
549 checkNotNull(ydtBuilder, "ydt cannot be null");
550
551 YdtContext ydtNode = ydtBuilder.getRootNode().getFirstChild();
552 YdtContextOperationType opType = ((YdtExtendedContext) ydtNode).getYdtContextOperationType();
553 while (opType == NONE) {
554 if (ydtNode.getYdtType() == YdtType.MULTI_INSTANCE_NODE) {
555 YdtContext firstChild = ydtNode.getFirstChild();
556 YdtContext lastChild = ydtNode.getLastChild();
557 if (firstChild.getYdtType() == YdtType.SINGLE_INSTANCE_LEAF_VALUE_NODE &&
558 ((YdtSingleInstanceLeafNode) firstChild).isKeyLeaf()) {
559 ydtNode = lastChild;
560 } else {
561 ydtNode = firstChild;
562 }
563 } else {
564 ydtNode = ydtNode.getFirstChild();
565 }
566
567 if (((YdtExtendedContext) ydtNode).getYdtContextOperationType() != NONE) {
568 // We found last node
569 break;
570 }
571
572 if (ydtNode == null) {
573 // There is no more node to find in YDT
574 return null;
575 }
576
577 opType = ((YdtExtendedContext) ydtNode).getYdtContextOperationType();
578 }
579
580 return ydtNode;
581 }
chengfanc58d4be2016-09-20 10:33:12 +0800582}