A race condition occurs between ARPHandler and HostTracker if the ARP
[controller.git] / opendaylight / sal / yang-prototype / yang / yang-model-util / src / main / java / org / opendaylight / controller / yang / model / util / SchemaContextUtil.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.yang.model.util;
9
10 import java.net.URI;
11 import java.util.LinkedList;
12 import java.util.List;
13 import java.util.Queue;
14 import java.util.Set;
15
16 import org.opendaylight.controller.yang.common.QName;
17 import org.opendaylight.controller.yang.model.api.*;
18
19 /**
20  * The Schema Context Util contains support methods for searching through Schema Context modules for specified schema
21  * nodes via Schema Path or Revision Aware XPath. The Schema Context Util is designed as mixin,
22  * so it is not instantiable.
23  *
24  * @author Lukas Sedlak <lsedlak@cisco.com>
25  */
26 public final class SchemaContextUtil {
27
28     private SchemaContextUtil() {
29     }
30
31     /**
32      * Method attempts to find DataSchemaNode in Schema Context via specified Schema Path. The returned
33      * DataSchemaNode from method will be the node at the end of the SchemaPath. If the DataSchemaNode is not present
34      * in the Schema Context the method will return <code>null</code>.
35      * <br>
36      * In case that Schema Context or Schema Path are not specified correctly (i.e. contains <code>null</code>
37      * values) the method will return IllegalArgumentException.
38      *
39      * @throws IllegalArgumentException
40      * 
41      * @param context
42      *            Schema Context
43      * @param schemaPath
44      *            Schema Path to search for
45      * @return DataSchemaNode from the end of the Schema Path or
46      *         <code>null</code> if the Node is not present.
47      */
48     public static DataSchemaNode findDataSchemaNode(final SchemaContext context, final SchemaPath schemaPath) {
49         if (context == null) {
50             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
51         }
52         if (schemaPath == null) {
53             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
54         }
55
56         final Module module = resolveModuleFromSchemaPath(context, schemaPath);
57         final Queue<QName> prefixedPath = new LinkedList<>(schemaPath.getPath());
58
59         if ((module != null) && (prefixedPath != null)) {
60             return findSchemaNodeForGivenPath(context, module, prefixedPath);
61         }
62         return null;
63     }
64
65     /**
66      * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
67      * Non-conditional Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise the
68      * operation would fail and return <code>null</code>.
69      * <br>
70      * The Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in path,
71      * because in this state the Schema Context is completely unaware of data state and will be not able to properly
72      * resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
73      * <br>
74      * In case that Schema Context or Module or Revision Aware XPath contains <code>null</code> references the method
75      * will throw IllegalArgumentException
76      * <br>
77      * If the Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in depending
78      * module in Schema Context the method will return specified Data Schema Node, otherwise the operation will fail
79      * and method will return <code>null</code>.
80      *
81      * @throws IllegalArgumentException
82      *
83      * @param context Schema Context
84      * @param module Yang Module
85      * @param nonCondXPath Non Conditional Revision Aware XPath
86      * @return Returns Data Schema Node for specified Schema Context for given Non-conditional Revision Aware XPath,
87      * or <code>null</code> if the DataSchemaNode is not present in Schema Context.
88      */
89     public static DataSchemaNode findDataSchemaNode(final SchemaContext context, final Module module,
90             final RevisionAwareXPath nonCondXPath) {
91         if (context == null) {
92             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
93         }
94         if (module == null) {
95             throw new IllegalArgumentException("Module reference cannot be NULL!");
96         }
97         if (nonCondXPath == null) {
98             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
99         }
100
101         final String strXPath = nonCondXPath.toString();
102         if (strXPath != null) {
103             if (strXPath.contains("[")) {
104                 throw new IllegalArgumentException("Revision Aware XPath cannot contains condition!");
105             }
106             if (nonCondXPath.isAbsolute()) {
107                 final Queue<QName> qnamedPath = xpathToQNamePath(context, module, strXPath);
108                 if (qnamedPath != null) {
109                     final DataSchemaNode dataNode = findSchemaNodeForGivenPath(context, module, qnamedPath);
110                     return dataNode;
111                 }
112             }
113         }
114         return null;
115     }
116
117     /**
118      * Method attempts to find DataSchemaNode inside of provided Schema Context and Yang Module accordingly to
119      * Non-conditional relative Revision Aware XPath. The specified Module MUST be present in Schema Context otherwise
120      * the operation would fail and return <code>null</code>.
121      * <br>
122      * The relative Revision Aware XPath MUST be specified WITHOUT the conditional statement (i.e. without [cond]) in
123      * path, because in this state the Schema Context is completely unaware of data state and will be not able to
124      * properly resolve XPath. If the XPath contains condition the method will return IllegalArgumentException.
125      * <br>
126      * The Actual Schema Node MUST be specified correctly because from this Schema Node will search starts. If the
127      * Actual Schema Node is not correct the operation will simply fail, because it will be unable to find desired
128      * DataSchemaNode.
129      * <br>
130      * In case that Schema Context or Module or Actual Schema Node or relative Revision Aware XPath contains
131      * <code>null</code> references the method will throw IllegalArgumentException
132      * <br>
133      * If the Revision Aware XPath doesn't have flag <code>isAbsolute == false</code> the method will
134      * throw IllegalArgumentException.
135      * <br>
136      * If the relative Revision Aware XPath is correct and desired Data Schema Node is present in Yang module or in
137      * depending module in Schema Context the method will return specified Data Schema Node,
138      * otherwise the operation will fail
139      * and method will return <code>null</code>.
140      *
141      * @throws IllegalArgumentException
142      *
143      * @param context Schema Context
144      * @param module Yang Module
145      * @param actualSchemaNode Actual Schema Node
146      * @param relativeXPath Relative Non Conditional Revision Aware XPath
147      * @return DataSchemaNode if is present in specified Schema Context for given relative Revision Aware XPath,
148      * otherwise will return <code>null</code>.
149      */
150     public static DataSchemaNode findDataSchemaNodeForRelativeXPath(final SchemaContext context, final Module module,
151             final SchemaNode actualSchemaNode, final RevisionAwareXPath relativeXPath) {
152         if (context == null) {
153             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
154         }
155         if (module == null) {
156             throw new IllegalArgumentException("Module reference cannot be NULL!");
157         }
158         if (actualSchemaNode == null) {
159             throw new IllegalArgumentException("Actual Schema Node reference cannot be NULL!");
160         }
161         if (relativeXPath == null) {
162             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
163         }
164         if (relativeXPath.isAbsolute()) {
165             throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
166                     + "for non relative Revision Aware XPath use findDataSchemaNode method!");
167         }
168
169         final SchemaPath actualNodePath = actualSchemaNode.getPath();
170         if (actualNodePath != null) {
171             final Queue<QName> qnamePath = resolveRelativeXPath(context, module, relativeXPath, actualNodePath);
172
173             if (qnamePath != null) {
174                 final DataSchemaNode dataNode = findSchemaNodeForGivenPath(context, module, qnamePath);
175                 return dataNode;
176             }
177         }
178         return null;
179     }
180
181     /**
182      * Retrieve information from Schema Path and returns the module reference to which Schema Node belongs. The
183      * search for correct Module is based on namespace within the last item in Schema Path. If schema context
184      * contains module with namespace specified in last item of Schema Path, then operation will returns Module
185      * reference, otherwise returns <code>null</code>
186      * <br>
187      * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
188      *
189      * @throws IllegalArgumentException
190      *
191      * @param context Schema Context
192      * @param schemaPath Schema Path
193      * @return Module reference for given Schema Path if module is present in Schema Context,
194      * otherwise returns <code>null</code>
195      */
196     private static Module resolveModuleFromSchemaPath(final SchemaContext context, final SchemaPath schemaPath) {
197         if (context == null) {
198             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
199         }
200         if (schemaPath == null) {
201             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
202         }
203
204         final List<QName> path = schemaPath.getPath();
205         if (!path.isEmpty()) {
206             final QName qname = path.get(path.size() - 1);
207
208             if ((qname != null) && (qname.getNamespace() != null)) {
209                 return context.findModuleByNamespace(qname.getNamespace());
210             }
211         }
212
213         return null;
214     }
215
216     /**
217      * Returns the Yang Module from specified Schema Context in which the TypeDefinition is declared. If the
218      * TypeDefinition si not present in Schema Context then the method will return <code>null</code>
219      *
220      * If Schema Context or TypeDefinition contains <code>null</code> references the method will throw IllegalArgumentException
221      *
222      * @throws IllegalArgumentException
223      *
224      * @param context Schema Context
225      * @param type Type Definition
226      * @return Yang Module in which the TypeDefinition is declared, if is not present, returns <code>null</code>.
227      */
228     public static Module findParentModuleForTypeDefinition(final SchemaContext context, final TypeDefinition<?> type) {
229         final SchemaPath schemaPath = type.getPath();
230         if (schemaPath == null) {
231             throw new IllegalArgumentException("Schema Path reference cannot be NULL");
232         }
233         final List<QName> qnamedPath = schemaPath.getPath();
234         if (qnamedPath == null || qnamedPath.isEmpty()) {
235             throw new IllegalStateException("Schema Path contains invalid state of path parts."
236                     + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
237                     + "of path.");
238         }
239
240         if (type instanceof ExtendedType) {
241             final QName qname = qnamedPath.get(qnamedPath.size() - 1);
242             if ((qname != null) && (qname.getNamespace() != null)) {
243                 return context.findModuleByNamespace(qname.getNamespace());
244             }
245         } else {
246             final QName qname = qnamedPath.get(qnamedPath.size() - 2);
247             if ((qname != null) && (qname.getNamespace() != null)) {
248                 return context.findModuleByNamespace(qname.getNamespace());
249             }
250         }
251         return null;
252     }
253
254     /**
255      * Returns parent Yang Module for specified Schema Context in which Schema Node is declared. If the Schema Node
256      * is not present in Schema Context the operation will return <code>null</code>.
257      * <br>
258      * If Schema Context or Schema Node contains <code>null</code> references the method will throw IllegalArgumentException
259      *
260      * @throws IllegalArgumentException
261      *
262      * @param context Schema Context
263      * @param schemaNode Schema Node
264      * @return Yang Module for specified Schema Context and Schema Node, if Schema Node is NOT present,
265      * the method will returns <code>null</code>
266      */
267     public static Module findParentModule(final SchemaContext context, final SchemaNode schemaNode) {
268         if (context == null) {
269             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
270         }
271         if (schemaNode == null) {
272             throw new IllegalArgumentException("Schema Node cannot be NULL!");
273         }
274
275         final SchemaPath schemaPath = schemaNode.getPath();
276         if (schemaPath == null) {
277             throw new IllegalStateException("Schema Path for Schema Node is not "
278                     + "set properly (Schema Path is NULL)");
279         }
280         final List<QName> qnamedPath = schemaPath.getPath();
281         if (qnamedPath == null || qnamedPath.isEmpty()) {
282             throw new IllegalStateException("Schema Path contains invalid state of path parts."
283                     + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
284                     + "of path.");
285         }
286         final QName qname = qnamedPath.get(qnamedPath.size() - 1);
287         return context.findModuleByNamespace(qname.getNamespace());
288     }
289
290     /**
291      * Method will attempt to find DataSchemaNode from specified Module and Queue of QNames through the Schema
292      * Context. The QNamed path could be defined across multiple modules in Schema Context so the method is called
293      * recursively. If the QNamed path contains QNames that are not part of any Module or Schema Context Path the
294      * operation will fail and returns <code>null</code>
295      * <br>
296      * If Schema Context, Module or Queue of QNames refers to <code>null</code> values,
297      * the method will throws IllegalArgumentException
298      *
299      * @throws IllegalArgumentException
300      *
301      * @param context Schema Context
302      * @param module Yang Module
303      * @param qnamedPath Queue of QNames
304      * @return DataSchemaNode if is present in Module(s) for specified Schema Context and given QNamed Path,
305      * otherwise will return <code>null</code>.
306      */
307     private static DataSchemaNode findSchemaNodeForGivenPath(final SchemaContext context, final Module module,
308             final Queue<QName> qnamedPath) {
309         if (context == null) {
310             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
311         }
312         if (module == null) {
313             throw new IllegalArgumentException("Module reference cannot be NULL!");
314         }
315         if (module.getNamespace() == null) {
316             throw new IllegalArgumentException("Namespace for Module cannot contains NULL reference!");
317         }
318         if (qnamedPath == null || qnamedPath.isEmpty()) {
319             throw new IllegalStateException("Schema Path contains invalid state of path parts."
320                     + "The Schema Path MUST contain at least ONE QName which defines namespace and Local name"
321                     + "of path.");
322         }
323
324         DataNodeContainer nextNode = module;
325         final URI moduleNamespace = module.getNamespace();
326
327         QName childNodeQName;
328         DataSchemaNode schemaNode = null;
329         while ((nextNode != null) && !qnamedPath.isEmpty()) {
330             childNodeQName = qnamedPath.peek();
331             if (childNodeQName != null) {
332                 final URI childNodeNamespace = childNodeQName.getNamespace();
333
334                 schemaNode = nextNode.getDataChildByName(childNodeQName);
335                 if (schemaNode != null) {
336                     if (schemaNode instanceof ContainerSchemaNode) {
337                         nextNode = (ContainerSchemaNode) schemaNode;
338                     } else if (schemaNode instanceof ListSchemaNode) {
339                         nextNode = (ListSchemaNode) schemaNode;
340                     } else if (schemaNode instanceof ChoiceNode) {
341                         final ChoiceNode choice = (ChoiceNode) schemaNode;
342                         qnamedPath.poll();
343                         if (!qnamedPath.isEmpty()) {
344                             childNodeQName = qnamedPath.peek();
345                             nextNode = choice.getCaseNodeByName(childNodeQName);
346                             schemaNode = (DataSchemaNode) nextNode;
347                         }
348                     } else {
349                         nextNode = null;
350                     }
351                 } else if (!childNodeNamespace.equals(moduleNamespace)) {
352                     final Module nextModule = context.findModuleByNamespace(childNodeNamespace);
353                     schemaNode = findSchemaNodeForGivenPath(context, nextModule, qnamedPath);
354                     return schemaNode;
355                 }
356                 qnamedPath.poll();
357             }
358         }
359         return schemaNode;
360     }
361
362     /**
363      * Transforms string representation of XPath to Queue of QNames. The XPath is split by "/" and for each part of
364      * XPath is assigned correct module in Schema Path.
365      * <br>
366      * If Schema Context, Parent Module or XPath string contains <code>null</code> values,
367      * the method will throws IllegalArgumentException
368      *
369      * @throws IllegalArgumentException
370      *
371      * @param context Schema Context
372      * @param parentModule Parent Module
373      * @param xpath XPath String
374      * @return
375      */
376     private static Queue<QName> xpathToQNamePath(final SchemaContext context, final Module parentModule,
377             final String xpath) {
378         if (context == null) {
379             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
380         }
381         if (parentModule == null) {
382             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
383         }
384         if (xpath == null) {
385             throw new IllegalArgumentException("XPath string reference cannot be NULL!");
386         }
387
388         final Queue<QName> path = new LinkedList<>();
389         final String[] prefixedPath = xpath.split("/");
390         for (int i = 0; i < prefixedPath.length; ++i) {
391             if (!prefixedPath[i].isEmpty()) {
392                 path.add(stringPathPartToQName(context, parentModule, prefixedPath[i]));
393             }
394         }
395         return path;
396     }
397
398     /**
399      * Transforms part of Prefixed Path as java String to QName.
400      * <br>
401      * If the string contains module prefix separated by ":" (i.e. mod:container) this module is provided from from
402      * Parent Module list of imports. If the Prefixed module is present in Schema Context the QName can be
403      * constructed.
404      * <br>
405      * If the Prefixed Path Part does not contains prefix the Parent's Module namespace is taken for construction of
406      * QName.
407      * <br>
408      * If Schema Context, Parent Module or Prefixed Path Part refers to <code>null</code> the method will throw
409      * IllegalArgumentException
410      *
411      * @throws IllegalArgumentException
412      *
413      * @param context Schema Context
414      * @param parentModule Parent Module
415      * @param prefixedPathPart Prefixed Path Part string
416      * @return QName from prefixed Path Part String.
417      */
418     private static QName stringPathPartToQName(final SchemaContext context, final Module parentModule,
419             final String prefixedPathPart) {
420         if (context == null) {
421             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
422         }
423         if (parentModule == null) {
424             throw new IllegalArgumentException("Parent Module reference cannot be NULL!");
425         }
426         if (prefixedPathPart == null) {
427             throw new IllegalArgumentException("Prefixed Path Part cannot be NULL!");
428         }
429
430         if (prefixedPathPart.contains(":")) {
431             final String[] prefixedName = prefixedPathPart.split(":");
432             final Module module = resolveModuleForPrefix(context, parentModule, prefixedName[0]);
433             if (module != null) {
434                 return new QName(module.getNamespace(), module.getRevision(), prefixedName[1]);
435             }
436         } else {
437             return new QName(parentModule.getNamespace(), parentModule.getRevision(), prefixedPathPart);
438         }
439         return null;
440     }
441
442     /**
443      * Method will attempt to resolve and provide Module reference for specified module prefix. Each Yang module
444      * could contains multiple imports which MUST be associated with corresponding module prefix. The method simply
445      * looks into module imports and returns the module that is bounded with specified prefix. If the prefix is not
446      * present in module or the prefixed module is not present in specified Schema Context,
447      * the method will return <code>null</code>.
448      * <br>
449      * If String prefix is the same as prefix of the specified Module the reference to this module is returned.
450      * <br>
451      * If Schema Context, Module or Prefix are referring to <code>null</code> the method will return
452      * IllegalArgumentException
453      *
454      * @throws IllegalArgumentException
455      *
456      * @param context Schema Context
457      * @param module Yang Module
458      * @param prefix Module Prefix
459      * @return Module for given prefix in specified Schema Context if is present, otherwise returns <code>null</code>
460      */
461     private static Module resolveModuleForPrefix(final SchemaContext context, final Module module, final String prefix) {
462         if (context == null) {
463             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
464         }
465         if (module == null) {
466             throw new IllegalArgumentException("Module reference cannot be NULL!");
467         }
468         if (prefix == null) {
469             throw new IllegalArgumentException("Prefix string cannot be NULL!");
470         }
471
472         if (prefix.equals(module.getPrefix())) {
473             return module;
474         }
475
476         final Set<ModuleImport> imports = module.getImports();
477         for (final ModuleImport mi : imports) {
478             if (prefix.equals(mi.getPrefix())) {
479                 return context.findModuleByName(mi.getModuleName(), mi.getRevision());
480             }
481         }
482         return null;
483     }
484
485     /**
486      * @throws IllegalArgumentException
487      *
488      * @param context Schema Context
489      * @param module Yang Module
490      * @param relativeXPath Non conditional Revision Aware Relative XPath
491      * @param leafrefSchemaPath Schema Path for Leafref
492      * @return
493      */
494     private static Queue<QName> resolveRelativeXPath(final SchemaContext context, final Module module,
495             final RevisionAwareXPath relativeXPath, final SchemaPath leafrefSchemaPath) {
496         final Queue<QName> absolutePath = new LinkedList<>();
497         if (context == null) {
498             throw new IllegalArgumentException("Schema Context reference cannot be NULL!");
499         }
500         if (module == null) {
501             throw new IllegalArgumentException("Module reference cannot be NULL!");
502         }
503         if (relativeXPath == null) {
504             throw new IllegalArgumentException("Non Conditional Revision Aware XPath cannot be NULL!");
505         }
506         if (relativeXPath.isAbsolute()) {
507             throw new IllegalArgumentException("Revision Aware XPath MUST be relative i.e. MUST contains ../, "
508                     + "for non relative Revision Aware XPath use findDataSchemaNode method!");
509         }
510         if (leafrefSchemaPath == null) {
511             throw new IllegalArgumentException("Schema Path reference for Leafref cannot be NULL!");
512         }
513
514         final String strXPath = relativeXPath.toString();
515         if (strXPath != null) {
516             final String[] xpaths = strXPath.split("/");
517             if (xpaths != null) {
518                 int colCount = 0;
519                 while (xpaths[colCount].contains("..")) {
520                     ++colCount;
521                 }
522                 final List<QName> path = leafrefSchemaPath.getPath();
523                 if (path != null) {
524                     int lenght = path.size() - colCount - 1;
525                     for (int i = 0; i < lenght; ++i) {
526                         absolutePath.add(path.get(i));
527                     }
528                     for (int i = colCount; i < xpaths.length; ++i) {
529                         absolutePath.add(stringPathPartToQName(context, module, xpaths[i]));
530                     }
531                 }
532             }
533         }
534         return absolutePath;
535     }
536 }