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