Replacing ":" by "_" for parameters
[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             Collection<PropertiesNode> childsFromAugmentation = parent
182                     .augmentations().get(augSchema);
183             if (!childsFromAugmentation.isEmpty()) {
184                 for (PropertiesNode pNode : childsFromAugmentation) {
185                     if (pNode.name().equals(name)) {
186                         return pNode;
187                     }
188                 }
189             }
190         }
191         return null;
192     }
193
194     /**
195      * Creates uri with specified name and namespace.
196      *
197      * @param parent parent properties node
198      * @param name name of the node
199      * @param ns namespace of the node
200      * @return uri with specified name and namespace
201      */
202     public static String getUri(PropertiesNode parent, String name,
203                                 Namespace ns) {
204         String uri = name;
205         if (!(parent.namespace().moduleNs().equals(ns.moduleNs()))) {
206             uri = ns.moduleName() + ":" + name;
207         }
208         return parent.uri() + "." + uri;
209     }
210
211     /**
212      * Creates new properties with specified parameters.
213      *
214      * @param name name of the properties node
215      * @param namespace namespace of the properties node
216      * @param uri uri of the properties node
217      * @param parent parent node
218      * @param appInfo application info
219      * @param type node type
220      * @return new properties node
221      */
222     public static PropertiesNode createNode(String name, Namespace namespace,
223                                             String uri, PropertiesNode parent,
224                                             Object appInfo, NodeType type) {
225         switch (type) {
226             case SINGLE_INSTANCE_NODE:
227                 return new SingleInstanceNode(name, namespace, uri, parent, appInfo, type);
228             case MULTI_INSTANCE_HOLDER_NODE:
229                 return new ListHolderNode(name, namespace, uri, parent, appInfo, type);
230             case MULTI_INSTANCE_LEAF_HOLDER_NODE:
231                 return new LeafListHolderNode(name, namespace, uri, parent, appInfo, type);
232             default:
233                 throw new RuntimeException("Invalid node type");
234         }
235     }
236
237     /**
238      * Returns true if namespace is same as parent's namespace.
239      *
240      * @param parent parent property node
241      * @param curNode current property node
242      * @return true if namespace is same as parent namespace
243      */
244     public static boolean isNamespaceAsParent(PropertiesNode parent,
245                                               PropertiesNode curNode) {
246         return parent.namespace().moduleNs().equals(curNode.namespace().moduleNs());
247     }
248
249     /**
250      * Returns the schema path holder with a formatted url and the instance
251      * identifier context from a given uri or the parameters from svc logic
252      * context.
253      *
254      * @param uri     unformatted uri or parameter
255      * @param context schema context
256      * @return schema path holder
257      */
258     public static SchemaPathHolder getProcessedPath(String uri,
259                                                     SchemaContext context) {
260
261         String uri1 = uri.replaceAll(UNDERSCORE, COLON);
262         try {
263             InstanceIdentifierContext<?> id = toInstanceIdentifier(
264                     uri1, context, null);
265             return new SchemaPathHolder(id, uri1);
266         } catch (IllegalArgumentException | RestconfDocumentedException
267                 | NullPointerException e) {
268             return processNodesAndAppendPath(uri, context);
269         }
270     }
271
272     /**
273      * Processes the nodes in the given uri and finds instance identifier
274      * context till it reaches the last node in uri. If its not able to find
275      * schema for the path, it appends the suffix part and puts it back in
276      * the param list.
277      *
278      * @param uri     uri with underscore
279      * @param context schema context
280      * @return schema and path holder
281      */
282     private static SchemaPathHolder processNodesAndAppendPath(String uri,
283                                                               SchemaContext context) {
284
285         String actPath = "";
286         SchemaPathHolder id = new SchemaPathHolder(null, "");
287         String[] uriParts = uri.split(SLASH);
288         String sec = "";
289         if (uri.contains(UNDERSCORE)) {
290             sec = uri.substring(uriParts[0].length()+1);
291         }
292         for (int i = 0; i<uriParts.length; i++) {
293
294             try {
295                 id = processIdentifier(uriParts[i], context, actPath);
296             } catch (IllegalArgumentException e) {
297                 id.setUri(actPath+ uriParts[i] + sec);
298                 return id;
299             }
300
301             actPath = actPath + id.getUri() + SLASH;
302             if (sec.startsWith(SLASH)) {
303                 sec = sec.replaceFirst(SLASH, "");
304             }
305             if (i+1 < uriParts.length) {
306                 sec = sec.replaceFirst(quote(uriParts[i + 1]), "");
307             }
308         }
309         id.setUri(actPath.substring(0,actPath.length() - 1));
310         return id;
311     }
312
313     /**
314      * Processes the schema and path holder for a given node in the path. It
315      * figures if the path is valid by replacing underscore in the node
316      * consecutively, till it finds the proper schema for the node.
317      *
318      * @param node    node in the path
319      * @param context schema context
320      * @param prefix  prefix for the node in the path
321      * @return schema and path holder
322      */
323     private static SchemaPathHolder processIdentifier(String node,
324                                                       SchemaContext context,
325                                                       String prefix) {
326
327         String[] values = node.split(UNDERSCORE);
328         String val = values[0];
329         StringBuilder firstHalf = new StringBuilder();
330         String secondHalf = "";
331         if (node.contains(UNDERSCORE)) {
332             secondHalf = node.substring(values[0].length()+1);
333         }
334         InstanceIdentifierContext<?> id;
335         for (int i = 0; i< values.length-1; i++) {
336             val = values[i];
337             val = firstHalf + val + COLON + secondHalf;
338             try {
339                 id = toInstanceIdentifier(prefix + val, context, null);
340                 return new SchemaPathHolder(id, val);
341             } catch (IllegalArgumentException | RestconfDocumentedException |
342                     NullPointerException e) {
343                 log.info(format(INFO_MSG, val));
344             }
345             firstHalf.append(values[i]).append(UNDERSCORE);
346             secondHalf = secondHalf.replaceFirst(
347                     values[i + 1] + UNDERSCORE,"");
348         }
349         val = val.replace(COLON,UNDERSCORE);
350         try {
351             id = toInstanceIdentifier(prefix + val, context, null);
352             return new SchemaPathHolder(id, val);
353         } catch (IllegalArgumentException | RestconfDocumentedException |
354                 NullPointerException e1) {
355             throw new IllegalArgumentException(EXC_MSG, e1);
356         }
357     }
358
359     /**
360      * Returns the namespace of the given node name. If the node name is
361      * separated by colon, the it splits with colon and forms the namespace.
362      * If the node name is formed with underscore, then it splits the node
363      * name consecutively to figure out the proper module name.
364      *
365      * @param childName node name
366      * @param ctx       schema context
367      * @param parent    parent properties node
368      * @param curSchema current schema
369      * @return namespace of the given node
370      */
371     static Namespace getNamespace(String childName, SchemaContext ctx,
372                                   PropertiesNode parent, SchemaNode curSchema) {
373
374         Namespace parentNs = parent.namespace();
375         Namespace ns = new Namespace(parentNs.moduleName(),
376                                      parentNs.moduleNs(), parentNs.revision());
377         int lastIndexOfColon = childName.lastIndexOf(COLON);
378         if (lastIndexOfColon != -1) {
379             String moduleName = childName.substring(0, lastIndexOfColon);
380             childName = childName.substring(lastIndexOfColon+1);
381             Namespace ns1 = getNs(moduleName, ctx);
382             if (ns1 != null) {
383                 ns = ns1;
384             }
385         }
386
387         SchemaNode child = getChildSchemaNode(curSchema, childName, ns);
388
389         if (child == null && childName.contains(UNDERSCORE)) {
390             String[] children = childName.split(UNDERSCORE);
391             String second = childName.substring(children[0].length() + 1);
392             StringBuilder first = new StringBuilder();
393
394             for (int i =0; i< children.length; i++) {
395                 String moduleName = first + children[i];
396                 Namespace newNs = getNs(moduleName, ctx);
397                 if (newNs != null) {
398                     return newNs;
399                 }
400                 first.append(children[i]).append(UNDERSCORE);
401                 if (i + 1 < children.length) {
402                     second = second.replaceFirst(
403                             children[i + 1] + UNDERSCORE, "");
404                 }
405             }
406             return ns;
407         }
408         return ns;
409     }
410
411     /**
412      * Returns the namespace by finding the given module in the schema context.
413      *
414      * @param modName module name
415      * @param ctx     schema context
416      * @return namespace of the given node name
417      */
418     private static Namespace getNs(String modName, SchemaContext ctx) {
419         Iterator<Module> it = ctx.findModules(modName).iterator();
420         if (it.hasNext()) {
421             Module m = it.next();
422             return new Namespace(modName, m.getQNameModule().getNamespace(),
423                                  getRevision(m.getRevision()));
424         }
425         return null;
426     }
427
428     /**
429      * Returns child schema node.
430      *
431      * @param curSchema current schema node
432      * @param name name of the property
433      * @param namespace namespace of the property
434      * @return child schema node
435      */
436     public static SchemaNode getChildSchemaNode(SchemaNode curSchema,
437                                                 String name,
438                                                 Namespace namespace) {
439         if (namespace == null) {
440             return null;
441         }
442
443         QName qname =  QName.create(namespace.moduleNs(),
444                                     Revision.of(namespace.revision()), name);
445
446         // YANG RPC will not be instance of DataSchemaNode
447         if (curSchema instanceof DataSchemaNode) {
448             Deque<DataSchemaNode> schemaNodeDeque = ParserStreamUtils.
449                     findSchemaNodeByNameAndNamespace(((DataSchemaNode)
450                             curSchema), name, namespace.moduleNs());
451             if (schemaNodeDeque.isEmpty()) {
452                 // could not find schema node
453                 return null;
454             }
455
456             DataSchemaNode schemaNode = schemaNodeDeque.pop();
457             if (schemaNodeDeque.isEmpty()){
458                 // Simple node
459                 return schemaNode;
460             }
461
462             // node is child of Choice/case
463             return SchemaUtils.findSchemaForChild(((ChoiceSchemaNode) schemaNode),
464                                                   qname);
465         } else {
466             return SchemaUtils.findDataChildSchemaByQName(curSchema, qname);
467         }
468     }
469
470     /**
471      * Returns the property node type.
472      *
473      * @param index current index
474      * @param length length of the properties
475      * @param name name of the property
476      * @return the property node type
477      */
478     public static NodeType getNodeType(int index, int length, String name) {
479         if (index == length-1) {
480             return (isListEntry(name) ? NodeType.MULTI_INSTANCE_LEAF_NODE :
481                     NodeType.SINGLE_INSTANCE_LEAF_NODE);
482         } else {
483             return (isListEntry(name) ? NodeType.MULTI_INSTANCE_NODE :
484                     NodeType.SINGLE_INSTANCE_NODE);
485         }
486     }
487
488     /**
489      * Returns revision in string.
490      *
491      * @param r YANG revision
492      * @return revision in string
493      */
494     public static String getRevision(Optional<Revision> r) {
495         return (r.isPresent()) ? r.get().toString() : null;
496     }
497
498     /**
499      * Returns value namespace for leaf value.
500      *
501      * @param value value of the leaf
502      * @param ctx schema context
503      * @return value namespace
504      * @throws SvcLogicException if identity/module could not be found
505      */
506     static Namespace getValueNamespace(String value,
507                                               SchemaContext ctx)
508             throws SvcLogicException {
509         String prefix = getPrefixFromValue(value);
510         if (prefix == null) {
511             return null;
512         }
513
514         IdentitySchemaNode id = IdentityCodecUtil.parseIdentity(value,
515                                                                 ctx,
516                                                                 prefixToModule -> {
517             final Iterator<Module> modules = ctx.findModules(prefix).iterator();
518             checkArgument(modules.hasNext(), "Could not find " +
519                                   "module %s", prefix);
520             return modules.next().getQNameModule();
521         });
522
523         if (id == null) {
524             throw new SvcLogicException("Could not find identity");
525         }
526
527         return getModuleNamespace(id.getQName(), ctx);
528     }
529
530     private static String getPrefixFromValue(String value) {
531         int lastIndexOfColon = value.lastIndexOf(":");
532         if (lastIndexOfColon != -1) {
533             return value.substring(0, lastIndexOfColon);
534         }
535         return null;
536     }
537
538     /**
539      * Returns module namespace from a given qName.
540      *
541      * @param qName qName of a node
542      * @param ctx   schema context
543      * @return module namespace of the node
544      * @throws SvcLogicException when the module is not available
545      */
546     public static Namespace getModuleNamespace(QName qName, SchemaContext ctx)
547             throws SvcLogicException {
548         Optional<Module> module = ctx.findModule(qName.getModule());
549         if (!module.isPresent()) {
550             throw new SvcLogicException("Could not find module node");
551         }
552         Module m = module.get();
553         return new Namespace(m.getName(), m.getQNameModule().getNamespace(),
554                              getRevision(m.getRevision()));
555     }
556
557     static String getParsedValue(Namespace valNs, String value) {
558         if (valNs != null && value.contains(":")) {
559             String[] valArr = value.split(":");
560             return valArr[1];
561         }
562         return value;
563     }
564 }