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.LeafNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
38 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
39 import org.opendaylight.yangtools.yang.model.api.Module;
40 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
41 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
42 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
43 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
45 import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
47 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.util.RegexUtils;
52 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
55 * A {@link FunctionContext} which contains also YANG-specific functions current(), re-match(), deref(),
56 * derived-from(), derived-from-or-self(), enum-value() and bit-is-set().
58 final class YangFunctionContext implements FunctionContext {
59 private static final Splitter COLON_SPLITTER = Splitter.on(':');
60 private static final Double DOUBLE_NAN = Double.NaN;
62 // Core XPath functions, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
63 private static final FunctionContext XPATH_FUNCTION_CONTEXT = new XPathFunctionContext(false);
64 // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
65 private static final Function CURRENT_FUNCTION = (context, args) -> {
66 if (!args.isEmpty()) {
67 throw new FunctionCallException("current() takes no arguments.");
70 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
71 return (NormalizedNodeContext) context;
74 // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1
75 private static final Function REMATCH_FUNCTION = (context, args) -> {
76 if (args == null || args.size() != 2) {
77 throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern.");
80 if (!(args.get(0) instanceof String)) {
81 throw new FunctionCallException("First argument of re-match() should be a String.");
84 if (!(args.get(1) instanceof String)) {
85 throw new FunctionCallException("Second argument of re-match() should be a String.");
88 final String subject = (String) args.get(0);
89 final String rawPattern = (String) args.get(1);
91 final String pattern = RegexUtils.getJavaRegexFromXSD(rawPattern);
93 return (Boolean) subject.matches(pattern);
96 // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1
97 private static final Function DEREF_FUNCTION = (context, args) -> {
98 if (!args.isEmpty()) {
99 throw new FunctionCallException("deref() takes only one argument: node-set nodes.");
102 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
104 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
105 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
106 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
109 final Object nodeValue = currentNodeContext.getNode().getValue();
111 if (correspondingSchemaNode.getType() instanceof InstanceIdentifierTypeDefinition
112 && nodeValue instanceof YangInstanceIdentifier) {
113 return getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext);
116 if (correspondingSchemaNode.getType() instanceof LeafrefTypeDefinition) {
117 final LeafrefTypeDefinition leafrefType = (LeafrefTypeDefinition) correspondingSchemaNode.getType();
118 final RevisionAwareXPath xpath = leafrefType.getPathStatement();
119 return getNodeReferencedByLeafref(xpath, currentNodeContext, schemaContext, correspondingSchemaNode,
126 private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path,
127 final NormalizedNodeContext currentNodeContext) {
128 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
129 final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
130 final List<PathArgument> pathArguments = path.getPathArguments();
131 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
132 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
133 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
134 if (possibleNode.isPresent()) {
135 return possibleNode.get();
142 private static NormalizedNode<?, ?> getNodeReferencedByLeafref(final RevisionAwareXPath xpath,
143 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
144 final TypedSchemaNode correspondingSchemaNode, final Object nodeValue) {
145 final NormalizedNode<?, ?> referencedNode = xpath.isAbsolute() ? getNodeReferencedByAbsoluteLeafref(xpath,
146 currentNodeContext, schemaContext, correspondingSchemaNode) : getNodeReferencedByRelativeLeafref(xpath,
147 currentNodeContext, schemaContext, correspondingSchemaNode);
149 if (referencedNode instanceof LeafSetNode) {
150 return getReferencedLeafSetEntryNode((LeafSetNode<?>) referencedNode, nodeValue);
153 if (referencedNode instanceof LeafNode && referencedNode.getValue().equals(nodeValue)) {
154 return referencedNode;
160 private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xpath,
161 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
162 final TypedSchemaNode correspondingSchemaNode) {
163 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
164 xpath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
165 final List<PathArgument> pathArguments = builder.build();
166 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
167 final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
168 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
169 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
170 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
171 if (possibleNode.isPresent()) {
172 return possibleNode.get();
179 private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xpath,
180 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
181 final TypedSchemaNode correspondingSchemaNode) {
182 NormalizedNodeContext relativeNodeContext = currentNodeContext;
183 final StringBuilder xPathStringBuilder = new StringBuilder(xpath.toString());
184 // strip the relative path of all ../ at the beginning
185 while (xPathStringBuilder.indexOf("../") == 0) {
186 xPathStringBuilder.delete(0, 3);
187 relativeNodeContext = relativeNodeContext.getParent();
190 // add / to the beginning of the path so that it can be processed the same way as an absolute path
191 xPathStringBuilder.insert(0, '/');
192 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
193 xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
194 final List<PathArgument> pathArguments = builder.build();
195 final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode();
196 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments);
197 if (possibleNode.isPresent()) {
198 return possibleNode.get();
204 private static LeafSetEntryNode<?> getReferencedLeafSetEntryNode(final LeafSetNode<?> referencedNode,
205 final Object currentNodeValue) {
206 for (final LeafSetEntryNode<?> entryNode : referencedNode.getValue()) {
207 if (currentNodeValue.equals(entryNode.getValue())) {
215 // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1
216 private static final Function DERIVED_FROM_FUNCTION = (context, args) -> {
217 if (args == null || args.size() != 1) {
218 throw new FunctionCallException("derived-from() takes two arguments: node-set nodes, string identity.");
221 if (!(args.get(0) instanceof String)) {
222 throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String.");
225 final String identityArg = (String) args.get(0);
227 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
229 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
230 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
231 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
234 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
235 return Boolean.FALSE;
238 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
239 return Boolean.FALSE;
242 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
244 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
245 correspondingSchemaNode);
246 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
249 final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
250 collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
252 return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
255 // derived-from-or-self(node-set nodes, string identity) function as per
256 // https://tools.ietf.org/html/rfc7950#section-10.4.2
257 private static final Function DERIVED_FROM_OR_SELF_FUNCTION = (context, args) -> {
258 if (args == null || args.size() != 1) {
259 throw new FunctionCallException(
260 "derived-from-or-self() takes two arguments: node-set nodes, string identity");
263 if (!(args.get(0) instanceof String)) {
264 throw new FunctionCallException(
265 "Argument 'identity' of derived-from-or-self() function should be a String.");
268 final String identityArg = (String) args.get(0);
270 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
272 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
273 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
274 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
277 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
278 return Boolean.FALSE;
281 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
282 return Boolean.FALSE;
285 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
287 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
288 correspondingSchemaNode);
289 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
291 if (currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)) {
295 final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
296 collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
298 return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode));
301 private static void collectAncestorIdentities(final IdentitySchemaNode identity,
302 final Set<IdentitySchemaNode> ancestorIdentities) {
303 for (final IdentitySchemaNode id : identity.getBaseIdentities()) {
304 collectAncestorIdentities(id, ancestorIdentities);
305 ancestorIdentities.add(id);
309 private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName,
310 final SchemaContext schemaContext) {
311 final Module module = schemaContext.findModuleByNamespaceAndRevision(identityQName.getNamespace(),
312 identityQName.getRevision());
313 return findIdentitySchemaNodeInModule(module, identityQName);
316 private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity,
317 final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode) {
318 final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity);
319 final Module module = schemaContext.findModuleByNamespaceAndRevision(
320 correspondingSchemaNode.getQName().getNamespace(), correspondingSchemaNode.getQName().getRevision());
321 if (identityPrefixAndName.size() == 2) {
322 // prefix of local module
323 if (identityPrefixAndName.get(0).equals(module.getPrefix())) {
324 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
325 identityPrefixAndName.get(1)));
328 // prefix of imported module
329 for (final ModuleImport moduleImport : module.getImports()) {
330 if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) {
331 final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(),
332 moduleImport.getRevision());
333 return findIdentitySchemaNodeInModule(importedModule, QName.create(
334 importedModule.getQNameModule(), identityPrefixAndName.get(1)));
338 throw new IllegalArgumentException(String.format("Cannot resolve prefix '%s' from identity '%s'.",
339 identityPrefixAndName.get(0), identity));
342 if (identityPrefixAndName.size() == 1) { // without prefix
343 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
344 identityPrefixAndName.get(0)));
347 throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
350 private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
351 for (final IdentitySchemaNode id : module.getIdentities()) {
352 if (identityQName.equals(id.getQName())) {
357 throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding"
358 + " identity schema node in the module %s.", identityQName, module));
361 // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1
362 private static final Function ENUM_VALUE_FUNCTION = (context, args) -> {
363 if (!args.isEmpty()) {
364 throw new FunctionCallException("enum-value() takes one argument: node-set nodes.");
367 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
369 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
370 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
371 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
374 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
375 if (!(nodeType instanceof EnumTypeDefinition)) {
379 final Object nodeValue = currentNodeContext.getNode().getValue();
380 if (!(nodeValue instanceof String)) {
384 final EnumTypeDefinition enumerationType = (EnumTypeDefinition) nodeType;
385 final String enumName = (String) nodeValue;
387 return getEnumValue(enumerationType, enumName);
390 private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) {
391 for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) {
392 if (enumName.equals(enumPair.getName())) {
393 return enumPair.getValue();
397 throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
398 enumName, enumerationType));
401 // bit-is-set(node-set nodes, string bit-name) function as per
402 // https://tools.ietf.org/html/rfc7950#section-10.6.1
403 private static final Function BIT_IS_SET_FUNCTION = (context, args) -> {
404 if (args == null || args.size() != 1) {
405 throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name");
408 if (!(args.get(0) instanceof String)) {
409 throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
412 final String bitName = (String) args.get(0);
414 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
416 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
417 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
418 final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
421 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
422 if (!(nodeType instanceof BitsTypeDefinition)) {
423 return Boolean.FALSE;
426 final Object nodeValue = currentNodeContext.getNode().getValue();
427 if (!(nodeValue instanceof Set)) {
428 return Boolean.FALSE;
431 final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
432 Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
434 return Boolean.valueOf(((Set<?>)nodeValue).contains(bitName));
437 private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
438 for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
439 if (bitName.equals(bit.getName())) {
447 private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) {
448 final ContextSupport contextSupport = normalizedNodeContext.getContextSupport();
449 Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s",
450 contextSupport.getClass());
451 return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext();
454 private static TypedSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext,
455 final NormalizedNodeContext currentNodeContext) {
456 Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator;
458 ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator()
459 .getAncestorOrSelfAxisIterator(currentNodeContext);
460 } catch (UnsupportedAxisException ex) {
461 throw new JaxenRuntimeException(ex);
464 final Deque<QName> schemaPathToCurrentNode = new ArrayDeque<>();
465 while (ancestorOrSelfAxisIterator.hasNext()) {
466 final NormalizedNode<?, ?> nextNode = ancestorOrSelfAxisIterator.next().getNode();
467 if (!(nextNode instanceof MapNode) && !(nextNode instanceof LeafSetNode)
468 && !(nextNode instanceof AugmentationNode)) {
469 schemaPathToCurrentNode.addFirst(nextNode.getNodeType());
473 final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode);
475 Preconditions.checkNotNull(schemaNode, "Node %s does not have a corresponding SchemaNode in the SchemaContext.",
476 currentNodeContext.getNode());
477 Preconditions.checkState(schemaNode instanceof TypedSchemaNode, "Node %s must be a leaf or a leaf-list.",
478 currentNodeContext.getNode());
479 return (TypedSchemaNode) schemaNode;
482 // Singleton instance of reuse
483 private static final YangFunctionContext INSTANCE = new YangFunctionContext();
485 private YangFunctionContext() {
488 static YangFunctionContext getInstance() {
493 public Function getFunction(final String namespaceURI, final String prefix, final String localName)
494 throws UnresolvableException {
495 if (prefix == null) {
498 return BIT_IS_SET_FUNCTION;
500 return CURRENT_FUNCTION;
502 return DEREF_FUNCTION;
504 return DERIVED_FROM_FUNCTION;
505 case "derived-from-or-self":
506 return DERIVED_FROM_OR_SELF_FUNCTION;
508 return ENUM_VALUE_FUNCTION;
510 return REMATCH_FUNCTION;
516 return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);