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