Merge "[CCSDK-1241] Increase GRToolkit Unit Test Coverage"
[ccsdk/sli/plugins.git] / restconf-client / provider / src / main / java / org / onap / ccsdk / sli / plugins / yangserializers / pnserializer / MdsalPropertiesNodeUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - CCSDK
4  * ================================================================================
5  * Copyright (C) 2018 Huawei Technologies Co., Ltd. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20
21 package org.onap.ccsdk.sli.plugins.yangserializers.pnserializer;
22
23 import java.util.Collection;
24 import java.util.Deque;
25 import java.util.Iterator;
26 import java.util.Optional;
27
28 import org.onap.ccsdk.sli.core.sli.SvcLogicException;
29 import org.opendaylight.restconf.common.context.InstanceIdentifierContext;
30 import org.opendaylight.restconf.common.errors.RestconfDocumentedException;
31 import org.opendaylight.yangtools.yang.common.QName;
32 import org.opendaylight.yangtools.yang.common.Revision;
33 import org.opendaylight.yangtools.yang.data.impl.schema.SchemaUtils;
34 import org.opendaylight.yangtools.yang.data.util.ParserStreamUtils;
35 import org.opendaylight.yangtools.yang.data.util.codec.IdentityCodecUtil;
36 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.Module;
41 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
42 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 import static com.google.common.base.Preconditions.checkArgument;
47 import static java.lang.String.format;
48 import static java.util.regex.Pattern.quote;
49 import static org.opendaylight.restconf.nb.rfc8040.utils.parser.ParserIdentifier.toInstanceIdentifier;
50
51 /**
52  * Represents utilities for properties node tree.
53  */
54 public final class MdsalPropertiesNodeUtils {
55
56     static final String COLON = ":";
57
58     static final String UNDERSCORE = "_";
59
60     static final String SLASH = "/";
61
62     static final String DOT_REGEX = "\\.";
63
64     private static final String INFO_MSG = "The %s formed is currently not" +
65             " valid";
66
67     private static final String EXC_MSG = "Unable to form a formatted path";
68
69     /**
70      * Logger for the Mdsal properties util class.
71      */
72     private static final Logger log = LoggerFactory.getLogger(
73             MdsalPropertiesNodeUtils.class);
74
75     private MdsalPropertiesNodeUtils() {
76     }
77
78     /**
79      * Returns the index from multi instance property name.
80      *
81      * @param name name of the property
82      * @return index from multi instance property name
83      */
84     public static String getIndex(String name) {
85         return name.substring(name.indexOf("[") + 1,
86                               name.indexOf("]"));
87     }
88
89     /**
90      * Returns the multi instance property name.
91      *
92      * @param name name of the property
93      * @return the multi instance property name
94      */
95     public static String getListName(String name) {
96         String[] s = name.split("\\[");
97         return s[0];
98     }
99
100     /**
101      * Returns true if property is multi instance.
102      *
103      * @param name name of the property
104      * @return true if property is multi instance
105      */
106     public static boolean isListEntry(String name) {
107         String s[] = name.split("\\[");
108         return s.length > 1;
109     }
110
111     /**
112      * Returns name of the property after pruning namespace and
113      * index if the property is multi instance.
114      *
115      * @param name name of the property
116      * @return name of the property
117      */
118     static String resolveName(String name) {
119         String localName = getListName(name);
120         final int lastIndexOfColon = localName.lastIndexOf(":");
121         if (lastIndexOfColon != -1) {
122             localName = localName.substring(lastIndexOfColon + 1);
123         }
124         return localName;
125     }
126
127     /**
128      * Returns name of the property after pruning namespace and index if the
129      * property is multi instance by knowing the module name from namespace.
130      *
131      * @param ns   namespace
132      * @param name name of the node
133      * @return resolved name
134      */
135     static String resolveName(Namespace ns, String name) {
136         String localName = getListName(name);
137         String modName = ns.moduleName();
138         if ((localName.contains(COLON) || localName.contains(UNDERSCORE))
139                 && localName.startsWith(modName)) {
140             localName = localName.substring(modName.length()+1);
141         }
142         return localName;
143     }
144
145     /**
146      * Adds current node to parent's augmentation map.
147      *
148      * @param augSchema augment schema
149      * @param parent parent property node
150      * @param curNode current property node
151      */
152     public static void addToAugmentations(AugmentationSchemaNode augSchema,
153                                           PropertiesNode parent,
154                                           PropertiesNode curNode) {
155         Collection<PropertiesNode> childsFromAugmentation = parent
156                 .augmentations().get(augSchema);
157         if (!childsFromAugmentation.isEmpty()) {
158             for (PropertiesNode pNode : childsFromAugmentation) {
159                 if (pNode.name().equals(curNode.name())) {
160                     return;
161                 }
162             }
163         }
164         parent.augmentations().put(augSchema, curNode);
165     }
166
167
168     /**
169      * Returns augmented properties node if it is already
170      * added in properties tree.
171      *
172      * @param augSchema augmented schema node
173      * @param parent parent properties node
174      * @param name name of the properties
175      * @return augmented properties node if it is already added
176      */
177     public static PropertiesNode getAugmentationNode(
178             AugmentationSchemaNode augSchema,
179             PropertiesNode parent, String name) {
180         if (augSchema == null) {
181             return null;
182         }
183
184         Collection<PropertiesNode> childsFromAugmentation = parent
185             .augmentations().get(augSchema);
186         if (!childsFromAugmentation.isEmpty()) {
187             for (PropertiesNode pNode : childsFromAugmentation) {
188                 if (pNode.name().equals(name)) {
189                     return pNode;
190                 }
191             }
192         }
193
194         return null;
195     }
196
197     /**
198      * Creates uri with specified name and namespace.
199      *
200      * @param parent parent properties node
201      * @param name name of the node
202      * @param ns namespace of the node
203      * @return uri with specified name and namespace
204      */
205     public static String getUri(PropertiesNode parent, String name,
206                                 Namespace ns) {
207         String uri = name;
208         if (!(parent.namespace().moduleNs().equals(ns.moduleNs()))) {
209             uri = ns.moduleName() + ":" + name;
210         }
211         return parent.uri() + "." + uri;
212     }
213
214     /**
215      * Creates new properties with specified parameters.
216      *
217      * @param name name of the properties node
218      * @param namespace namespace of the properties node
219      * @param uri uri of the properties node
220      * @param parent parent node
221      * @param appInfo application info
222      * @param type node type
223      * @return new properties node
224      * @throws SvcLogicException exception while creating properties node
225      */
226     public static PropertiesNode createNode(String name, Namespace namespace,
227                                             String uri, PropertiesNode parent,
228                                             Object appInfo, NodeType type)
229             throws SvcLogicException {
230         switch (type) {
231             case SINGLE_INSTANCE_NODE:
232                 return new SingleInstanceNode(name, namespace, uri, parent, appInfo, type);
233             case MULTI_INSTANCE_HOLDER_NODE:
234                 return new ListHolderNode(name, namespace, uri, parent, appInfo, type);
235             case MULTI_INSTANCE_LEAF_HOLDER_NODE:
236                 return new LeafListHolderNode(name, namespace, uri, parent, appInfo, type);
237             default:
238                 throw new SvcLogicException("Invalid node type " + type);
239         }
240     }
241
242     /**
243      * Returns true if namespace is same as parent's namespace.
244      *
245      * @param parent parent property node
246      * @param curNode current property node
247      * @return true if namespace is same as parent namespace
248      */
249     public static boolean isNamespaceAsParent(PropertiesNode parent,
250                                               PropertiesNode curNode) {
251         return parent.namespace().moduleNs().equals(curNode.namespace().moduleNs());
252     }
253
254     /**
255      * Returns the schema path holder with a formatted url and the instance
256      * identifier context from a given uri or the parameters from svc logic
257      * context.
258      *
259      * @param uri     unformatted uri or parameter
260      * @param context schema context
261      * @return schema path holder
262      */
263     public static SchemaPathHolder getProcessedPath(String uri,
264                                                     SchemaContext context) {
265
266         String uri1 = uri.replaceAll(UNDERSCORE, COLON);
267         try {
268             InstanceIdentifierContext<?> id = toInstanceIdentifier(
269                     uri1, context, null);
270             return new SchemaPathHolder(id, uri1);
271         } catch (IllegalArgumentException | RestconfDocumentedException
272                 | NullPointerException e) {
273             log.info("Exception while converting uri to instance identifier" +
274                 " context. Process each node in uri to get instance identifier" +
275                 " context " + e);
276             return processNodesAndAppendPath(uri, context);
277         }
278     }
279
280     /**
281      * Processes the nodes in the given uri and finds instance identifier
282      * context till it reaches the last node in uri. If its not able to find
283      * schema for the path, it appends the suffix part and puts it back in
284      * the param list.
285      *
286      * @param uri     uri with underscore
287      * @param context schema context
288      * @return schema and path holder
289      */
290     private static SchemaPathHolder processNodesAndAppendPath(String uri,
291                                                               SchemaContext context) {
292
293         String actPath = "";
294         SchemaPathHolder id = new SchemaPathHolder(null, "");
295         String[] uriParts = uri.split(SLASH);
296         String sec = "";
297         if (uri.contains(UNDERSCORE)) {
298             sec = uri.substring(uriParts[0].length()+1);
299         }
300         for (int i = 0; i<uriParts.length; i++) {
301
302             try {
303                 id = processIdentifier(uriParts[i], context, actPath);
304             } catch (IllegalArgumentException e) {
305                 log.info(format(EXC_MSG, e));
306                 id.setUri(actPath+ uriParts[i] + sec);
307                 return id;
308             }
309
310             actPath = actPath + id.getUri() + SLASH;
311             if (sec.startsWith(SLASH)) {
312                 sec = sec.replaceFirst(SLASH, "");
313             }
314             if (i+1 < uriParts.length) {
315                 sec = sec.replaceFirst(quote(uriParts[i + 1]), "");
316             }
317         }
318         id.setUri(actPath.substring(0,actPath.length() - 1));
319         return id;
320     }
321
322     /**
323      * Processes the schema and path holder for a given node in the path. It
324      * figures if the path is valid by replacing underscore in the node
325      * consecutively, till it finds the proper schema for the node.
326      *
327      * @param node    node in the path
328      * @param context schema context
329      * @param prefix  prefix for the node in the path
330      * @return schema and path holder
331      */
332     private static SchemaPathHolder processIdentifier(String node,
333                                                       SchemaContext context,
334                                                       String prefix) {
335
336         String[] values = node.split(UNDERSCORE);
337         String val = values[0];
338         StringBuilder firstHalf = new StringBuilder();
339         String secondHalf = "";
340         if (node.contains(UNDERSCORE)) {
341             secondHalf = node.substring(values[0].length()+1);
342         }
343         InstanceIdentifierContext<?> id;
344         for (int i = 0; i< values.length-1; i++) {
345             val = values[i];
346             val = firstHalf + val + COLON + secondHalf;
347             try {
348                 id = toInstanceIdentifier(prefix + val, context, null);
349                 return new SchemaPathHolder(id, val);
350             } catch (IllegalArgumentException | RestconfDocumentedException |
351                     NullPointerException e) {
352                 log.info(format(INFO_MSG, val, e));
353             }
354             firstHalf.append(values[i]).append(UNDERSCORE);
355             secondHalf = secondHalf.replaceFirst(
356                     values[i + 1] + UNDERSCORE,"");
357         }
358         val = val.replace(COLON,UNDERSCORE);
359         try {
360             id = toInstanceIdentifier(prefix + val, context, null);
361             return new SchemaPathHolder(id, val);
362         } catch (IllegalArgumentException | RestconfDocumentedException |
363                 NullPointerException e1) {
364             throw new IllegalArgumentException(EXC_MSG, e1);
365         }
366     }
367
368     /**
369      * Returns the namespace of the given node name. If the node name is
370      * separated by colon, the it splits with colon and forms the namespace.
371      * If the node name is formed with underscore, then it splits the node
372      * name consecutively to figure out the proper module name.
373      *
374      * @param childName node name
375      * @param ctx       schema context
376      * @param parent    parent properties node
377      * @param curSchema current schema
378      * @return namespace of the given node
379      */
380     static Namespace getNamespace(String childName, SchemaContext ctx,
381                                   PropertiesNode parent, SchemaNode curSchema) {
382
383         Namespace parentNs = parent.namespace();
384         Namespace ns = new Namespace(parentNs.moduleName(),
385                                      parentNs.moduleNs(), parentNs.revision());
386         int lastIndexOfColon = childName.lastIndexOf(COLON);
387         if (lastIndexOfColon != -1) {
388             String moduleName = childName.substring(0, lastIndexOfColon);
389             childName = childName.substring(lastIndexOfColon+1);
390             Namespace ns1 = getNs(moduleName, ctx);
391             if (ns1 != null) {
392                 ns = ns1;
393             }
394         }
395
396         SchemaNode child = getChildSchemaNode(curSchema, childName, ns);
397
398         if (child == null && childName.contains(UNDERSCORE)) {
399             String[] children = childName.split(UNDERSCORE);
400             String second = childName.substring(children[0].length() + 1);
401             StringBuilder first = new StringBuilder();
402
403             for (int i =0; i< children.length; i++) {
404                 String moduleName = first + children[i];
405                 Namespace newNs = getNs(moduleName, ctx);
406                 if (newNs != null) {
407                     return newNs;
408                 }
409                 first.append(children[i]).append(UNDERSCORE);
410                 if (i + 1 < children.length) {
411                     second = second.replaceFirst(
412                             children[i + 1] + UNDERSCORE, "");
413                 }
414             }
415             return ns;
416         }
417         return ns;
418     }
419
420     /**
421      * Returns the namespace by finding the given module in the schema context.
422      *
423      * @param modName module name
424      * @param ctx     schema context
425      * @return namespace of the given node name
426      */
427     private static Namespace getNs(String modName, SchemaContext ctx) {
428         Iterator<Module> it = ctx.findModules(modName).iterator();
429         if (it.hasNext()) {
430             Module m = it.next();
431             return new Namespace(modName, m.getQNameModule().getNamespace(),
432                                  getRevision(m.getRevision()));
433         }
434         return null;
435     }
436
437     /**
438      * Returns child schema node.
439      *
440      * @param curSchema current schema node
441      * @param name name of the property
442      * @param namespace namespace of the property
443      * @return child schema node
444      */
445     public static SchemaNode getChildSchemaNode(SchemaNode curSchema,
446                                                 String name,
447                                                 Namespace namespace) {
448         if (namespace == null) {
449             return null;
450         }
451
452         QName qname =  QName.create(namespace.moduleNs(),
453                                     Revision.of(namespace.revision()), name);
454
455         // YANG RPC will not be instance of DataSchemaNode
456         if (curSchema instanceof DataSchemaNode) {
457             Deque<DataSchemaNode> schemaNodeDeque = ParserStreamUtils.
458                     findSchemaNodeByNameAndNamespace(((DataSchemaNode)
459                             curSchema), name, namespace.moduleNs());
460             if (schemaNodeDeque.isEmpty()) {
461                 // could not find schema node
462                 return null;
463             }
464
465             DataSchemaNode schemaNode = schemaNodeDeque.pop();
466             if (schemaNodeDeque.isEmpty()){
467                 // Simple node
468                 return schemaNode;
469             }
470
471             // node is child of Choice/case
472             return SchemaUtils.findSchemaForChild(((ChoiceSchemaNode) schemaNode),
473                                                   qname);
474         } else {
475             return SchemaUtils.findDataChildSchemaByQName(curSchema, qname);
476         }
477     }
478
479     /**
480      * Returns the property node type.
481      *
482      * @param index current index
483      * @param length length of the properties
484      * @param name name of the property
485      * @return the property node type
486      */
487     public static NodeType getNodeType(int index, int length, String name) {
488         if (index == length-1) {
489             return (isListEntry(name) ? NodeType.MULTI_INSTANCE_LEAF_NODE :
490                     NodeType.SINGLE_INSTANCE_LEAF_NODE);
491         } else {
492             return (isListEntry(name) ? NodeType.MULTI_INSTANCE_NODE :
493                     NodeType.SINGLE_INSTANCE_NODE);
494         }
495     }
496
497     /**
498      * Returns revision in string.
499      *
500      * @param r YANG revision
501      * @return revision in string
502      */
503     public static String getRevision(Optional<Revision> r) {
504         return (r.isPresent()) ? r.get().toString() : null;
505     }
506
507     /**
508      * Returns value namespace for leaf value.
509      *
510      * @param value value of the leaf
511      * @param ctx schema context
512      * @return value namespace
513      * @throws SvcLogicException if identity/module could not be found
514      */
515     static Namespace getValueNamespace(String value,
516                                               SchemaContext ctx)
517             throws SvcLogicException {
518         String prefix = getPrefixFromValue(value);
519         if (prefix == null) {
520             return null;
521         }
522
523         IdentitySchemaNode id = IdentityCodecUtil.parseIdentity(value,
524                                                                 ctx,
525                                                                 prefixToModule -> {
526             final Iterator<Module> modules = ctx.findModules(prefix).iterator();
527             checkArgument(modules.hasNext(), "Could not find " +
528                                   "module %s", prefix);
529             return modules.next().getQNameModule();
530         });
531
532         if (id == null) {
533             throw new SvcLogicException("Could not find identity");
534         }
535
536         return getModuleNamespace(id.getQName(), ctx);
537     }
538
539     private static String getPrefixFromValue(String value) {
540         int lastIndexOfColon = value.lastIndexOf(":");
541         if (lastIndexOfColon != -1) {
542             return value.substring(0, lastIndexOfColon);
543         }
544         return null;
545     }
546
547     /**
548      * Returns module namespace from a given qName.
549      *
550      * @param qName qName of a node
551      * @param ctx   schema context
552      * @return module namespace of the node
553      * @throws SvcLogicException when the module is not available
554      */
555     public static Namespace getModuleNamespace(QName qName, SchemaContext ctx)
556             throws SvcLogicException {
557         Optional<Module> module = ctx.findModule(qName.getModule());
558         if (!module.isPresent()) {
559             throw new SvcLogicException("Could not find module node");
560         }
561         Module m = module.get();
562         return new Namespace(m.getName(), m.getQNameModule().getNamespace(),
563                              getRevision(m.getRevision()));
564     }
565
566     static String getParsedValue(Namespace valNs, String value) {
567         if (valNs != null && value.contains(":")) {
568             String[] valArr = value.split(":");
569             return valArr[1];
570         }
571         return value;
572     }
573 }