2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.yangtools.yang.data.jaxen;
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;
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;
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().
56 final class YangFunctionContext implements FunctionContext {
57 private static final Splitter COLON_SPLITTER = Splitter.on(':');
58 private static final Double DOUBLE_NAN = Double.NaN;
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.");
68 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
69 return ((NormalizedNodeContext) context);
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.");
78 if (!(args.get(0) instanceof String)) {
79 throw new FunctionCallException("First argument of re-match() should be a String.");
82 if (!(args.get(1) instanceof String)) {
83 throw new FunctionCallException("Second argument of re-match() should be a String.");
86 final String subject = (String) args.get(0);
87 final String rawPattern = (String) args.get(1);
89 final String pattern = RegexUtils.getJavaRegexFromXSD(rawPattern);
91 return (Boolean) subject.matches(pattern);
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.");
100 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
102 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
103 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
104 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
106 final Object nodeValue = currentNodeContext.getNode().getValue();
108 if (correspondingSchemaNode.getType() instanceof InstanceIdentifierTypeDefinition
109 && nodeValue instanceof YangInstanceIdentifier) {
110 return getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext);
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;
123 final NormalizedNode<?, ?> referencedNode = getNodeReferencedByRelativeLeafref(
124 xPath, currentNodeContext, schemaContext, correspondingSchemaNode);
125 if (referencedNode.getValue().equals(nodeValue)) {
126 return referencedNode;
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();
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();
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();
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();
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.");
204 if (!(args.get(0) instanceof String)) {
205 throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String.");
208 final String identityArg = (String) args.get(0);
210 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
212 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
213 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
214 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
216 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
217 return Boolean.FALSE;
220 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
221 return Boolean.FALSE;
224 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
226 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
227 correspondingSchemaNode);
228 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
231 final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
232 collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
234 return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
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");
243 if (!(args.get(0) instanceof String)) {
244 throw new FunctionCallException("Argument 'identity' of derived-from-or-self() function should be a String.");
247 final String identityArg = (String) args.get(0);
249 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
251 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
252 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
253 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
255 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
256 return Boolean.FALSE;
259 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
260 return Boolean.FALSE;
263 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
265 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
266 correspondingSchemaNode);
267 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
269 if (currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)) {
273 final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
274 collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
276 return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
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);
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);
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)));
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)));
316 throw new IllegalArgumentException("Cannot resolve prefix '%s' from identity '%s'.");
319 if (identityPrefixAndName.size() == 1) { // without prefix
320 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
321 identityPrefixAndName.get(0)));
324 throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
327 private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
328 for (final IdentitySchemaNode id : module.getIdentities()) {
329 if (identityQName.equals(id.getQName())) {
334 throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding" +
335 " identity schema node in the module %s.", identityQName, module));
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.");
346 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
348 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
349 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
350 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
353 if (!(correspondingSchemaNode.getType() instanceof EnumTypeDefinition)) {
357 if (!(currentNodeContext.getNode().getValue() instanceof String)) {
361 final EnumTypeDefinition enumerationType = (EnumTypeDefinition) correspondingSchemaNode.getType();
362 final String enumName = (String) currentNodeContext.getNode().getValue();
364 return getEnumValue(enumerationType, enumName);
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();
374 throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
375 enumName, enumerationType));
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");
384 if (!(args.get(0) instanceof String)) {
385 throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
388 final String bitName = (String) args.get(0);
390 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
392 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
393 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
394 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext);
396 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
397 if (!(nodeType instanceof BitsTypeDefinition)) {
398 return Boolean.FALSE;
401 final Object nodeValue = currentNodeContext.getNode().getValue();
402 if (!(nodeValue instanceof Set)) {
403 return Boolean.FALSE;
406 final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
407 Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
409 return Boolean.valueOf(((Set<?>)nodeValue).contains(bitName));
412 private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
413 for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
414 if (bitName.equals(bit.getName())) {
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();
429 private static TypedSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext,
430 final NormalizedNodeContext currentNodeContext) {
431 Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator;
433 ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator()
434 .getAncestorOrSelfAxisIterator(currentNodeContext);
435 } catch (UnsupportedAxisException ex) {
436 throw new JaxenRuntimeException(ex);
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());
448 final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode);
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;
457 // Singleton instance of reuse
458 private static final YangFunctionContext INSTANCE = new YangFunctionContext();
460 private YangFunctionContext() {
463 static YangFunctionContext getInstance() {
468 public Function getFunction(final String namespaceURI, final String prefix, final String localName)
469 throws UnresolvableException {
470 if (prefix == null) {
473 return BIT_IS_SET_FUNCTION;
475 return CURRENT_FUNCTION;
477 return DEREF_FUNCTION;
479 return DERIVED_FROM_FUNCTION;
480 case "derived-from-or-self":
481 return DERIVED_FROM_OR_SELF_FUNCTION;
483 return ENUM_VALUE_FUNCTION;
485 return REMATCH_FUNCTION;
489 return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);