a335a5099f4d2e3dbdcb9acb4d4fa82bc5944548
[yangtools.git] / yang / yang-data-jaxen / src / main / java / org / opendaylight / yangtools / yang / data / jaxen / YangFunctionContext.java
1 /*
2  * Copyright (c) 2015 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.yangtools.yang.data.jaxen;
9
10 import com.google.common.base.Optional;
11 import com.google.common.base.Preconditions;
12 import com.google.common.base.Splitter;
13 import com.google.common.base.Verify;
14 import java.util.ArrayDeque;
15 import java.util.Deque;
16 import java.util.HashSet;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.Set;
20 import org.jaxen.ContextSupport;
21 import org.jaxen.Function;
22 import org.jaxen.FunctionCallException;
23 import org.jaxen.FunctionContext;
24 import org.jaxen.JaxenRuntimeException;
25 import org.jaxen.UnresolvableException;
26 import org.jaxen.UnsupportedAxisException;
27 import org.jaxen.XPathFunctionContext;
28 import org.opendaylight.yangtools.yang.common.QName;
29 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
31 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
32 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
36 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
37 import org.opendaylight.yangtools.yang.model.api.Module;
38 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
39 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
40 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
41 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
43 import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.util.RegexUtils;
50 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
51
52 /**
53  * A {@link FunctionContext} which contains also YANG-specific functions current(), re-match(), deref(),
54  * derived-from(), derived-from-or-self(), enum-value() and bit-is-set().
55  */
56 final class YangFunctionContext implements FunctionContext {
57     private static final Splitter COLON_SPLITTER = Splitter.on(':');
58     private static final Double DOUBLE_NAN = Double.NaN;
59
60     // Core XPath functions, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
61     private static final FunctionContext XPATH_FUNCTION_CONTEXT = new XPathFunctionContext(false);
62     // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
63     private static final Function CURRENT_FUNCTION = (context, args) -> {
64         if (!args.isEmpty()) {
65             throw new FunctionCallException("current() takes no arguments.");
66         }
67
68         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
69         return ((NormalizedNodeContext) context);
70     };
71
72     // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1
73     private static final Function REMATCH_FUNCTION = (context, args) -> {
74         if (args == null || args.size() != 2) {
75             throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern.");
76         }
77
78         if (!(args.get(0) instanceof String)) {
79             throw new FunctionCallException("First argument of re-match() should be a String.");
80         }
81
82         if (!(args.get(1) instanceof String)) {
83             throw new FunctionCallException("Second argument of re-match() should be a String.");
84         }
85
86         final String subject = (String) args.get(0);
87         final String rawPattern = (String) args.get(1);
88
89         final String pattern = RegexUtils.getJavaRegexFromXSD(rawPattern);
90
91         return (Boolean) subject.matches(pattern);
92     };
93
94     // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1
95     private static final Function DEREF_FUNCTION = (context, args) -> {
96         if (!args.isEmpty()) {
97             throw new FunctionCallException("deref() takes only one argument: node-set nodes.");
98         }
99
100         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
101
102         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
103         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
104         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
105
106         final Object nodeValue = currentNodeContext.getNode().getValue();
107
108         if (correspondingSchemaNode.getType() instanceof InstanceIdentifierTypeDefinition
109                 && nodeValue instanceof YangInstanceIdentifier) {
110             return getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext);
111         }
112
113         if (correspondingSchemaNode.getType() instanceof LeafrefTypeDefinition) {
114             final LeafrefTypeDefinition leafrefType = (LeafrefTypeDefinition) correspondingSchemaNode.getType();
115             final RevisionAwareXPath xPath = leafrefType.getPathStatement();
116             if (xPath.isAbsolute()) {
117                 final NormalizedNode<?, ?> referencedNode = getNodeReferencedByAbsoluteLeafref(
118                         xPath, currentNodeContext, schemaContext, correspondingSchemaNode);
119                 if (referencedNode.getValue().equals(nodeValue)) {
120                     return referencedNode;
121                 }
122             } else {
123                 final NormalizedNode<?, ?> referencedNode = getNodeReferencedByRelativeLeafref(
124                         xPath, currentNodeContext, schemaContext, correspondingSchemaNode);
125                 if (referencedNode.getValue().equals(nodeValue)) {
126                     return referencedNode;
127                 }
128             }
129         }
130
131         return null;
132     };
133
134     private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path,
135             final NormalizedNodeContext currentNodeContext) {
136         final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
137         final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
138         final List<PathArgument> pathArguments = path.getPathArguments();
139         if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
140             final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
141             final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
142             if (possibleNode.isPresent()) {
143                 return possibleNode.get();
144             }
145
146             return null;
147         }
148
149         return null;
150     }
151
152     private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xPath,
153             final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
154             final TypedSchemaNode correspondingSchemaNode) {
155         final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
156                 xPath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
157         final List<PathArgument> pathArguments = builder.build();
158         final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
159         final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
160         if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
161             final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
162             final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
163             if (possibleNode.isPresent()) {
164                 return possibleNode.get();
165             }
166
167             return null;
168         }
169
170         return null;
171     }
172
173     private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xPath,
174             final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
175             final TypedSchemaNode correspondingSchemaNode) {
176         NormalizedNodeContext relativeNodeContext = currentNodeContext;
177         final StringBuilder xPathStringBuilder = new StringBuilder(xPath.toString());
178         // strip the relative path of all ../ at the beginning
179         while (xPathStringBuilder.indexOf("../") == 0) {
180             xPathStringBuilder.delete(0, 3);
181             relativeNodeContext = relativeNodeContext.getParent();
182         }
183
184         // add / to the beginning of the path so that it can be processed the same way as an absolute path
185         xPathStringBuilder.insert(0, '/');
186         final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
187                 xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
188         final List<PathArgument> pathArguments = builder.build();
189         final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode();
190         final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments);
191         if (possibleNode.isPresent()) {
192             return possibleNode.get();
193         }
194
195         return null;
196     }
197
198     // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1
199     private static final Function DERIVED_FROM_FUNCTION = (context, args) -> {
200         if (args == null || args.size() != 1) {
201             throw new FunctionCallException("derived-from() takes two arguments: node-set nodes, string identity.");
202         }
203
204         if (!(args.get(0) instanceof String)) {
205             throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String.");
206         }
207
208         final String identityArg = (String) args.get(0);
209
210         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
211
212         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
213         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
214         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
215
216         if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
217             return Boolean.FALSE;
218         }
219
220         if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
221             return Boolean.FALSE;
222         }
223
224         final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
225
226         final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
227                 correspondingSchemaNode);
228         final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
229                 schemaContext);
230
231         final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
232         collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
233
234         return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
235     };
236
237     // derived-from-or-self(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.2
238     private static final Function DERIVED_FROM_OR_SELF_FUNCTION = (context, args) -> {
239         if (args == null || args.size() != 1) {
240             throw new FunctionCallException("derived-from-or-self() takes two arguments: node-set nodes, string identity");
241         }
242
243         if (!(args.get(0) instanceof String)) {
244             throw new FunctionCallException("Argument 'identity' of derived-from-or-self() function should be a String.");
245         }
246
247         final String identityArg = (String) args.get(0);
248
249         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
250
251         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
252         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
253         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
254
255         if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
256             return Boolean.FALSE;
257         }
258
259         if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
260             return Boolean.FALSE;
261         }
262
263         final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
264
265         final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
266                 correspondingSchemaNode);
267         final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
268                 schemaContext);
269         if (currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)) {
270             return Boolean.TRUE;
271         }
272
273         final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
274         collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
275
276         return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
277     };
278
279     private static void collectAncestorIdentities(final IdentitySchemaNode identity,
280             final Set<IdentitySchemaNode> ancestorIdentities) {
281         for (final IdentitySchemaNode id : identity.getBaseIdentities()) {
282             collectAncestorIdentities(id, ancestorIdentities);
283             ancestorIdentities.add(id);
284         }
285     }
286
287     private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName,
288             final SchemaContext schemaContext) {
289         final Module module = schemaContext.findModuleByNamespaceAndRevision(identityQName.getNamespace(),
290                 identityQName.getRevision());
291         return findIdentitySchemaNodeInModule(module, identityQName);
292     }
293
294     private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity,
295             final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode) {
296         final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity);
297         final Module module = schemaContext.findModuleByNamespaceAndRevision(
298                 correspondingSchemaNode.getQName().getNamespace(), correspondingSchemaNode.getQName().getRevision());
299         if (identityPrefixAndName.size() == 2) {
300             // prefix of local module
301             if (identityPrefixAndName.get(0).equals(module.getPrefix())) {
302                 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
303                         identityPrefixAndName.get(1)));
304             }
305
306             // prefix of imported module
307             for (final ModuleImport moduleImport : module.getImports()) {
308                 if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) {
309                     final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(),
310                         moduleImport.getRevision());
311                     return findIdentitySchemaNodeInModule(importedModule, QName.create(
312                         importedModule.getQNameModule(), identityPrefixAndName.get(1)));
313                 }
314             }
315
316             throw new IllegalArgumentException("Cannot resolve prefix '%s' from identity '%s'.");
317         }
318
319         if (identityPrefixAndName.size() == 1) { // without prefix
320             return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
321                     identityPrefixAndName.get(0)));
322         }
323
324         throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
325     }
326
327     private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
328         for (final IdentitySchemaNode id : module.getIdentities()) {
329             if (identityQName.equals(id.getQName())) {
330                 return id;
331             }
332         }
333
334         throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding" +
335                 " identity schema node in the module %s.", identityQName, module));
336     }
337
338
339
340     // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1
341     private static final Function ENUM_VALUE_FUNCTION = (context, args) -> {
342         if (!args.isEmpty()) {
343             throw new FunctionCallException("enum-value() takes one argument: node-set nodes.");
344         }
345
346         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
347
348         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
349         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
350         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
351             currentNodeContext);
352
353         if (!(correspondingSchemaNode.getType() instanceof EnumTypeDefinition)) {
354             return DOUBLE_NAN;
355         }
356
357         if (!(currentNodeContext.getNode().getValue() instanceof String)) {
358             return DOUBLE_NAN;
359         }
360
361         final EnumTypeDefinition enumerationType = (EnumTypeDefinition) correspondingSchemaNode.getType();
362         final String enumName = (String) currentNodeContext.getNode().getValue();
363
364         return getEnumValue(enumerationType, enumName);
365     };
366
367     private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) {
368         for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) {
369             if (enumName.equals(enumPair.getName())) {
370                 return enumPair.getValue();
371             }
372         }
373
374         throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
375                 enumName, enumerationType));
376     }
377
378     // bit-is-set(node-set nodes, string bit-name) function as per https://tools.ietf.org/html/rfc7950#section-10.6.1
379     private static final Function BIT_IS_SET_FUNCTION = (context, args) -> {
380         if (args == null || args.size() != 1) {
381             throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name");
382         }
383
384         if (!(args.get(0) instanceof String)) {
385             throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
386         }
387
388         final String bitName = (String) args.get(0);
389
390         Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
391
392         final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
393         final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
394         final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
395
396         final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
397         if (!(nodeType instanceof BitsTypeDefinition)) {
398             return Boolean.FALSE;
399         }
400
401         final Object nodeValue = currentNodeContext.getNode().getValue();
402         if (!(nodeValue instanceof Set)) {
403             return Boolean.FALSE;
404         }
405
406         final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
407         Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
408             bitsType);
409         return Boolean.valueOf(((Set<?>)nodeValue).contains(bitName));
410     };
411
412     private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
413         for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
414             if (bitName.equals(bit.getName())) {
415                 return true;
416             }
417         }
418
419         return false;
420     }
421
422     private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) {
423         final ContextSupport contextSupport = normalizedNodeContext.getContextSupport();
424         Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s",
425                 contextSupport.getClass());
426         return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext();
427     }
428
429     private static TypedSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext,
430             final NormalizedNodeContext currentNodeContext) {
431         Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator;
432         try {
433             ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator()
434                     .getAncestorOrSelfAxisIterator(currentNodeContext);
435         } catch (UnsupportedAxisException ex) {
436             throw new JaxenRuntimeException(ex);
437         }
438
439         final Deque<QName> schemaPathToCurrentNode = new ArrayDeque<>();
440         while (ancestorOrSelfAxisIterator.hasNext()) {
441             final NormalizedNode<?, ?> nextNode = ancestorOrSelfAxisIterator.next().getNode();
442             if (!(nextNode instanceof MapNode) && !(nextNode instanceof LeafSetNode)
443                     && !(nextNode instanceof AugmentationNode)) {
444                 schemaPathToCurrentNode.addFirst(nextNode.getNodeType());
445             }
446         }
447
448         final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode);
449
450         Preconditions.checkNotNull(schemaNode, "Node %s does not have a corresponding SchemaNode in the SchemaContext.",
451                 currentNodeContext.getNode());
452         Preconditions.checkState(schemaNode instanceof TypedSchemaNode, "Node %s must be a leaf or a leaf-list.",
453                 currentNodeContext.getNode());
454         return (TypedSchemaNode) schemaNode;
455     }
456
457     // Singleton instance of reuse
458     private static final YangFunctionContext INSTANCE = new YangFunctionContext();
459
460     private YangFunctionContext() {
461     }
462
463     static YangFunctionContext getInstance() {
464         return INSTANCE;
465     }
466
467     @Override
468     public Function getFunction(final String namespaceURI, final String prefix, final String localName)
469             throws UnresolvableException {
470         if (prefix == null) {
471             switch (localName) {
472                 case "bit-is-set":
473                     return BIT_IS_SET_FUNCTION;
474                 case "current":
475                     return CURRENT_FUNCTION;
476                 case "deref":
477                     return DEREF_FUNCTION;
478                 case "derived-from":
479                     return DERIVED_FROM_FUNCTION;
480                 case "derived-from-or-self":
481                     return DERIVED_FROM_OR_SELF_FUNCTION;
482                 case "enum-value":
483                     return ENUM_VALUE_FUNCTION;
484                 case "re-match":
485                     return REMATCH_FUNCTION;
486             }
487         }
488
489         return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);
490     }
491 }