blob: 2bf7f74256839aead105ea56620b68967b7a3370 [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;
chengfanc58d4be2016-09-20 10:33:12 +080025import org.onosproject.yms.ydt.YdtBuilder;
26import org.onosproject.yms.ydt.YdtContext;
27import org.onosproject.yms.ydt.YdtContextOperationType;
28import org.onosproject.yms.ydt.YdtListener;
29import org.onosproject.yms.ydt.YdtType;
30import org.onosproject.yms.ydt.YdtWalker;
31
32import java.io.UnsupportedEncodingException;
33import java.net.URLDecoder;
34import java.util.ArrayList;
35import java.util.List;
36
37import static com.google.common.base.Preconditions.checkNotNull;
38import static org.onosproject.yms.ydt.YdtContextOperationType.NONE;
39
40/**
41 * Utils to complete the conversion between JSON and YDT(YANG DATA MODEL).
42 */
43public final class ParserUtils {
44
45 private static final Splitter SLASH_SPLITTER = Splitter.on('/');
46 private static final Splitter COMMA_SPLITTER = Splitter.on(',');
47 private static final String EQUAL = "=";
48 private static final String COMMA = ",";
49 private static final String COLON = ":";
50 private static final String URI_ENCODING_CHAR_SET = "ISO-8859-1";
51 private static final String ERROR_LIST_MSG = "List/Leaf-list node should be " +
52 "in format \"nodeName=key\"or \"nodeName=instance-value\"";
53 private static final String ERROR_MODULE_MSG = "First node should be in " +
54 "format \"moduleName:nodeName\"";
55
56 // no instantiation
57 private ParserUtils() {
58 }
59
60 /**
61 * Converts URI identifier to YDT builder.
62 *
63 * @param id the uri identifier from web request
64 * @param builder the base ydt builder
65 * @param opType the ydt operation type for the uri
66 */
67 public static void convertUriToYdt(String id,
68 YdtBuilder builder,
69 YdtContextOperationType opType) {
70 checkNotNull(id, "uri identifier should not be null");
71 List<String> paths = urlPathArgsDecode(SLASH_SPLITTER.split(id));
72 if (!paths.isEmpty()) {
Henry Yu7dbfc202016-10-24 16:00:37 -040073 processPathSegments(paths, builder, opType, true);
chengfanc58d4be2016-09-20 10:33:12 +080074 }
75 }
76
77 /**
78 * Converts JSON objectNode to YDT builder. The objectNode can be any
79 * standard JSON node, node just for RESTconf payload.
80 *
81 * @param objectNode the objectNode from web request
82 * @param builder the base ydt builder
83 */
84 public static void convertJsonToYdt(ObjectNode objectNode,
85 YdtBuilder builder) {
86
87 JsonToYdtListener listener = new JsonToYdtListener(builder);
88 new DefaultJsonWalker().walk(listener, null, objectNode);
89 }
90
91 /**
92 * Converts a Ydt context tree to a JSON object.
93 *
94 * @param rootName the name of the YdtContext from which the YdtListener
95 * start to builder a Json Object
96 * @param context a abstract data model for YANG data
97 * @param walker abstraction of an entity which provides interfaces for
98 * YDT walk
99 * @return the JSON node corresponding the YANG data
100 */
101 public static ObjectNode convertYdtToJson(String rootName,
102 YdtContext context,
103 YdtWalker walker) {
104 JsonBuilder builder = new DefaultJsonBuilder();
105 YdtListener listener = new YdtToJsonListener(rootName, builder);
106 walker.walk(listener, context);
107 return builder.getTreeNode();
108 }
109
110 /**
111 * Converts a list of path segments to a YDT builder tree.
112 *
Henry Yu7dbfc202016-10-24 16:00:37 -0400113 * @param paths the list of path segments split from URI
114 * @param builder the base YDT builder
115 * @param opType the YDT operation type for the Path segment
116 * @param isFirstIteration true if paths contains all the URI segments
chengfanc58d4be2016-09-20 10:33:12 +0800117 * @return the YDT builder with the tree info of paths
118 */
119 private static YdtBuilder processPathSegments(List<String> paths,
120 YdtBuilder builder,
Henry Yu7dbfc202016-10-24 16:00:37 -0400121 YdtContextOperationType opType,
122 boolean isFirstIteration) {
chengfanc58d4be2016-09-20 10:33:12 +0800123 if (paths.isEmpty()) {
124 return builder;
125 }
chengfanc58d4be2016-09-20 10:33:12 +0800126
Henry Yu7dbfc202016-10-24 16:00:37 -0400127 boolean isLastSegment = paths.size() == 1;
chengfanc58d4be2016-09-20 10:33:12 +0800128
Henry Yu7dbfc202016-10-24 16:00:37 -0400129 /*
130 * Process the first segment in path.
131 *
132 * BUG ONOS-5500: YMS requires the treatment for the very first
133 * segment in the URI path to be different than the rest. So,
134 * we added a parameter, isFirstIteration, to this function.
135 * It is set to true by the caller when this function is called
136 * the very first time (i.e,, "paths" contains all the segments).
137 *
138 */
139 YdtContextOperationType opTypeForThisSegment = isLastSegment ? opType : NONE;
140 String segment = paths.iterator().next();
141 processSinglePathSegment(segment, builder, opTypeForThisSegment, isFirstIteration);
142
143 if (isLastSegment) {
144 // We have hit the base case of recursion.
chengfanc58d4be2016-09-20 10:33:12 +0800145 return builder;
146 }
Henry Yu7dbfc202016-10-24 16:00:37 -0400147
148 /*
149 * Chop off the first segment, and recursively process the rest
150 * of the path segments.
151 */
chengfanc58d4be2016-09-20 10:33:12 +0800152 List<String> remainPaths = paths.subList(1, paths.size());
Henry Yu7dbfc202016-10-24 16:00:37 -0400153 processPathSegments(remainPaths, builder, opType, false);
chengfanc58d4be2016-09-20 10:33:12 +0800154
155 return builder;
156 }
157
Henry Yu7dbfc202016-10-24 16:00:37 -0400158 private static void processSinglePathSegment(String pathSegment,
159 YdtBuilder builder,
160 YdtContextOperationType opType,
161 boolean isTopLevelSegment) {
162 if (pathSegment.contains(COLON)) {
163 processPathSegmentWithNamespace(pathSegment, builder, opType, isTopLevelSegment);
164 } else {
165 processPathSegmentWithoutNamespace(pathSegment, builder, opType);
166 }
167 }
168
169 private static void processPathSegmentWithNamespace(String pathSegment,
170 YdtBuilder builder,
171 YdtContextOperationType opType,
172 boolean isTopLevelSegment) {
173 if (isTopLevelSegment) {
174 /*
175 * BUG ONOS-5500: If this segment refers to the first node in
176 * the path (i.e., top level of the model hierarchy), then
177 * YMS requires 2 YDT nodes to be added instead of one. The
178 * first one contains the namespace, and the second contains
179 * the node name. For other segments in the path, only one
180 * YDT node is needed.
181 */
182 addModule(builder, pathSegment);
183 }
184
185 String nodeName = getLatterSegment(pathSegment, COLON);
186 String namespace = getPreSegment(pathSegment, COLON);
187 convertPathSegmentToYdtNode(nodeName, namespace, builder, opType);
188 }
189
190 private static void processPathSegmentWithoutNamespace(String pathSegment,
191 YdtBuilder builder,
192 YdtContextOperationType opType) {
193 convertPathSegmentToYdtNode(pathSegment, null, builder, opType);
194 }
195
196 private static void convertPathSegmentToYdtNode(String pathSegment,
197 String namespace,
198 YdtBuilder builder,
199 YdtContextOperationType opType) {
200 if (pathSegment.contains(EQUAL)) {
201 addListOrLeafList(pathSegment, namespace, builder, opType);
202 } else {
203 addLeaf(pathSegment, namespace, builder, opType);
204 }
205 }
206
chengfanc58d4be2016-09-20 10:33:12 +0800207 private static YdtBuilder addListOrLeafList(String path,
Henry Yu7dbfc202016-10-24 16:00:37 -0400208 String namespace,
chengfanc58d4be2016-09-20 10:33:12 +0800209 YdtBuilder builder,
210 YdtContextOperationType opType) {
211 String nodeName = getPreSegment(path, EQUAL);
212 String keyStr = getLatterSegment(path, EQUAL);
213 if (keyStr == null) {
214 throw new JsonParseException(ERROR_LIST_MSG);
215 }
216 builder.setDefaultEditOperationType(opType);
217 if (keyStr.contains(COMMA)) {
218 List<String> keys = Lists.
219 newArrayList(COMMA_SPLITTER.split(keyStr));
Henry Yu7dbfc202016-10-24 16:00:37 -0400220 builder.addMultiInstanceChild(nodeName, namespace, keys);
chengfanc58d4be2016-09-20 10:33:12 +0800221 } else {
Henry Yu7dbfc202016-10-24 16:00:37 -0400222 builder.addMultiInstanceChild(nodeName, namespace,
chengfanc58d4be2016-09-20 10:33:12 +0800223 Lists.newArrayList(keyStr));
224 }
225 return builder;
226 }
227
Henry Yu7dbfc202016-10-24 16:00:37 -0400228 private static YdtBuilder addLeaf(String path,
229 String namespace,
230 YdtBuilder builder,
chengfanc58d4be2016-09-20 10:33:12 +0800231 YdtContextOperationType opType) {
232 checkNotNull(path);
Henry Yu7dbfc202016-10-24 16:00:37 -0400233 builder.addChild(path, namespace, opType);
chengfanc58d4be2016-09-20 10:33:12 +0800234 return builder;
235 }
236
chengfan7b2a60b2016-10-08 20:27:15 +0800237 private static YdtBuilder addModule(YdtBuilder builder, String path) {
238 String moduleName = getPreSegment(path, COLON);
239 if (moduleName == null) {
240 throw new JsonParseException(ERROR_MODULE_MSG);
241 }
242 builder.addChild(moduleName, null, YdtType.SINGLE_INSTANCE_NODE);
243 return builder;
244 }
245
246 private static YdtBuilder addNode(String path, YdtBuilder builder,
247 YdtContextOperationType opType) {
248 String nodeName = getLatterSegment(path, COLON);
249 builder.addChild(nodeName,
250 null,
251 YdtType.SINGLE_INSTANCE_NODE,
252 opType);
253 return builder;
254 }
255
chengfanc58d4be2016-09-20 10:33:12 +0800256 /**
257 * Returns the previous segment of a path which is separated by a split char.
258 * For example:
259 * <pre>
260 * "foo:bar", ":" --> "foo"
261 * </pre>
262 *
263 * @param path the original path string
264 * @param splitChar char used to split the path
265 * @return the previous segment of the path
266 */
267 private static String getPreSegment(String path, String splitChar) {
268 int idx = path.indexOf(splitChar);
269 if (idx == -1) {
270 return null;
271 }
272
273 if (path.indexOf(splitChar, idx + 1) != -1) {
274 return null;
275 }
276
277 return path.substring(0, idx);
278 }
279
280 /**
281 * Returns the latter segment of a path which is separated by a split char.
282 * For example:
283 * <pre>
284 * "foo:bar", ":" --> "bar"
285 * </pre>
286 *
287 * @param path the original path string
288 * @param splitChar char used to split the path
289 * @return the latter segment of the path
290 */
291 private static String getLatterSegment(String path, String splitChar) {
292 int idx = path.indexOf(splitChar);
293 if (idx == -1) {
294 return path;
295 }
296
297 if (path.indexOf(splitChar, idx + 1) != -1) {
298 return null;
299 }
300
301 return path.substring(idx + 1);
302 }
303
304 /**
305 * Converts a list of path from the original format to ISO-8859-1 code.
306 *
307 * @param paths the original paths
308 * @return list of decoded paths
309 */
310 public static List<String> urlPathArgsDecode(Iterable<String> paths) {
311 try {
312 List<String> decodedPathArgs = new ArrayList<>();
313 for (String pathArg : paths) {
314 String decode = URLDecoder.decode(pathArg,
315 URI_ENCODING_CHAR_SET);
316 decodedPathArgs.add(decode);
317 }
318 return decodedPathArgs;
319 } catch (UnsupportedEncodingException e) {
320 throw new JsonParseException("Invalid URL path arg '" +
321 paths + "': ", e);
322 }
323 }
chengfan7b2a60b2016-10-08 20:27:15 +0800324
325 /**
326 * Converts a field to a simple YANG node description which contains the
327 * namespace and name information.
328 *
329 * @param field field name of a JSON body, or a segment of a URI
330 * in a request of RESTCONF
331 * @return a simple normalized YANG node
332 */
333 public static NormalizedYangNode buildNormalizedNode(String field) {
334 String namespace = getPreSegment(field, COLON);
335 String name = getLatterSegment(field, COLON);
336 return new NormalizedYangNode(namespace, name);
337 }
Henry Yu528007c2016-11-01 15:49:59 -0400338
339
340 /**
341 * Extracts the node name from a YDT node and encodes it in JSON format.
342 * A JSON encoded node name has the following format:
343 * <p>
344 * module_name ":" node_name
345 * <p>
346 * where module_name is name of the YANG module in which the data
347 * resource is defined, and node_name is the name of the data resource.
348 * <p>
349 * If the YDT node is null or its node name field is null, then the function
350 * returns null. If the node name field is not null but module name field is,
351 * then the function returns only the node name.
352 *
353 * @param ydtContext YDT node of the target data resource
354 * @return JSON encoded name of the target data resource
355 */
356 public static String getJsonNameFromYdtNode(YdtContext ydtContext) {
357 if (ydtContext == null) {
358 return null;
359 }
360
361 String nodeName = ydtContext.getName();
362 if (nodeName == null) {
363 return null;
364 }
365
366 /*
367 * The namespace field in YDT node is a string which contains a list
368 * of identifiers separated by colon (:). e.g.,
369 *
370 * {identifier ":" identifier}+
371 *
372 * The last identifier in the string is the YANG module name.
373 */
374 String moduleName = getModuleNameFromNamespace(ydtContext.getNamespace());
375 if (moduleName == null) {
376 return nodeName;
377 } else {
378 return moduleName + COLON + nodeName;
379 }
380 }
381
382 private static String getModuleNameFromNamespace(String namespace) {
383 if (namespace == null) {
384 return null;
385 }
386
387 String moduleName = null;
388
389 if (namespace.contains(COLON)) {
390 String[] tokens = namespace.split(COLON);
391 moduleName = tokens[tokens.length - 1];
392 }
393
394 return moduleName;
395 }
chengfanc58d4be2016-09-20 10:33:12 +0800396}