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.Preconditions;
11 import com.google.common.base.Splitter;
12 import com.google.common.base.Verify;
13 import java.util.ArrayDeque;
14 import java.util.Deque;
15 import java.util.HashSet;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.Optional;
20 import org.jaxen.Context;
21 import org.jaxen.ContextSupport;
22 import org.jaxen.Function;
23 import org.jaxen.FunctionCallException;
24 import org.jaxen.FunctionContext;
25 import org.jaxen.JaxenRuntimeException;
26 import org.jaxen.UnresolvableException;
27 import org.jaxen.UnsupportedAxisException;
28 import org.jaxen.XPathFunctionContext;
29 import org.opendaylight.yangtools.yang.common.QName;
30 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
31 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
32 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
33 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
34 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
35 import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode;
36 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
37 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes;
39 import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.Module;
41 import org.opendaylight.yangtools.yang.model.api.ModuleImport;
42 import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath;
43 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
44 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
46 import org.opendaylight.yangtools.yang.model.api.TypedDataSchemaNode;
47 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition;
48 import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition;
49 import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition;
50 import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition;
51 import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition;
52 import org.opendaylight.yangtools.yang.model.util.RegexUtils;
53 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
56 * A {@link FunctionContext} which contains also YANG-specific functions current(), re-match(), deref(),
57 * derived-from(), derived-from-or-self(), enum-value() and bit-is-set().
59 final class YangFunctionContext implements FunctionContext {
60 private static final Splitter COLON_SPLITTER = Splitter.on(':');
61 private static final Double DOUBLE_NAN = Double.NaN;
63 // Core XPath functions, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
64 private static final FunctionContext XPATH_FUNCTION_CONTEXT = new XPathFunctionContext(false);
66 // Singleton instance of reuse
67 private static final YangFunctionContext INSTANCE = new YangFunctionContext();
69 private YangFunctionContext() {
72 static YangFunctionContext getInstance() {
77 public Function getFunction(final String namespaceURI, final String prefix, final String localName)
78 throws UnresolvableException {
82 return YangFunctionContext::bitIsSet;
84 return YangFunctionContext::current;
86 return YangFunctionContext::deref;
88 return YangFunctionContext::derivedFrom;
89 case "derived-from-or-self":
90 return YangFunctionContext::derivedFromOrSelf;
92 return YangFunctionContext::enumValueFunction;
94 return YangFunctionContext::reMatch;
100 return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);
103 // bit-is-set(node-set nodes, string bit-name) function as per
104 // https://tools.ietf.org/html/rfc7950#section-10.6.1
105 private static boolean bitIsSet(final Context context, final List<?> args) throws FunctionCallException {
106 if (args == null || args.size() != 1) {
107 throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name");
110 if (!(args.get(0) instanceof String)) {
111 throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
114 final String bitName = (String) args.get(0);
116 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
118 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
119 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
120 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
123 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
124 if (!(nodeType instanceof BitsTypeDefinition)) {
128 final Object nodeValue = currentNodeContext.getNode().getValue();
129 if (!(nodeValue instanceof Set)) {
133 final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
134 Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
136 return ((Set<?>)nodeValue).contains(bitName);
139 // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
140 private static NormalizedNodeContext current(final Context context, final List<?> args)
141 throws FunctionCallException {
142 if (!args.isEmpty()) {
143 throw new FunctionCallException("current() takes no arguments.");
146 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
147 return (NormalizedNodeContext) context;
150 // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1
151 private static NormalizedNode<?, ?> deref(final Context context, final List<?> args) throws FunctionCallException {
152 if (!args.isEmpty()) {
153 throw new FunctionCallException("deref() takes only one argument: node-set nodes.");
156 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
157 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
158 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
159 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
162 final Object nodeValue = currentNodeContext.getNode().getValue();
163 final TypeDefinition<?> type = correspondingSchemaNode.getType();
164 if (type instanceof InstanceIdentifierTypeDefinition) {
165 return nodeValue instanceof YangInstanceIdentifier
166 ? getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext)
169 if (type instanceof LeafrefTypeDefinition) {
170 final RevisionAwareXPath xpath = ((LeafrefTypeDefinition) type).getPathStatement();
171 return getNodeReferencedByLeafref(xpath, currentNodeContext, schemaContext, correspondingSchemaNode,
177 // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1
178 private static boolean derivedFrom(final Context context, final List<?> args) throws FunctionCallException {
179 if (args == null || args.size() != 1) {
180 throw new FunctionCallException("derived-from() takes two arguments: node-set nodes, string identity.");
183 if (!(args.get(0) instanceof String)) {
184 throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String.");
187 final String identityArg = (String) args.get(0);
189 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
191 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
192 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
193 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
196 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
200 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
204 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
206 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
207 correspondingSchemaNode);
208 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
211 final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
212 collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
214 return ancestorIdentities.contains(identityArgSchemaNode);
217 // derived-from-or-self(node-set nodes, string identity) function as per
218 // https://tools.ietf.org/html/rfc7950#section-10.4.2
219 private static boolean derivedFromOrSelf(final Context context, final List<?> args) throws FunctionCallException {
220 if (args == null || args.size() != 1) {
221 throw new FunctionCallException(
222 "derived-from-or-self() takes two arguments: node-set nodes, string identity");
225 if (!(args.get(0) instanceof String)) {
226 throw new FunctionCallException(
227 "Argument 'identity' of derived-from-or-self() function should be a String.");
230 final String identityArg = (String) args.get(0);
232 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
234 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
235 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
236 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
239 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
243 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
247 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
249 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
250 correspondingSchemaNode);
251 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
253 if (currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)) {
257 final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>();
258 collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities);
260 return ancestorIdentities.contains(identityArgSchemaNode);
263 // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1
264 private static Object enumValueFunction(final Context context, final List<?> args) throws FunctionCallException {
265 if (!args.isEmpty()) {
266 throw new FunctionCallException("enum-value() takes one argument: node-set nodes.");
269 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
271 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
272 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
273 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
276 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
277 if (!(nodeType instanceof EnumTypeDefinition)) {
281 final Object nodeValue = currentNodeContext.getNode().getValue();
282 if (!(nodeValue instanceof String)) {
286 final EnumTypeDefinition enumerationType = (EnumTypeDefinition) nodeType;
287 final String enumName = (String) nodeValue;
289 return getEnumValue(enumerationType, enumName);
292 // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1
293 private static boolean reMatch(final Context context, final List<?> args) throws FunctionCallException {
294 if (args == null || args.size() != 2) {
295 throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern.");
297 final Object subject = args.get(0);
298 if (!(subject instanceof String)) {
299 throw new FunctionCallException("First argument of re-match() should be a String.");
301 final Object pattern = args.get(1);
302 if (!(pattern instanceof String)) {
303 throw new FunctionCallException("Second argument of re-match() should be a String.");
306 return ((String) subject).matches(RegexUtils.getJavaRegexFromXSD((String) pattern));
309 private static void collectAncestorIdentities(final IdentitySchemaNode identity,
310 final Set<IdentitySchemaNode> ancestorIdentities) {
311 for (final IdentitySchemaNode id : identity.getBaseIdentities()) {
312 collectAncestorIdentities(id, ancestorIdentities);
313 ancestorIdentities.add(id);
317 private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName,
318 final SchemaContext schemaContext) {
319 final Optional<Module> module = schemaContext.findModule(identityQName.getModule());
320 Preconditions.checkArgument(module.isPresent(), "Module for %s not found", identityQName);
321 return findIdentitySchemaNodeInModule(module.get(), identityQName);
324 private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity,
325 final SchemaContext schemaContext, final TypedDataSchemaNode correspondingSchemaNode) {
326 final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity);
327 final Module module = schemaContext.findModule(correspondingSchemaNode.getQName().getModule()).get();
328 if (identityPrefixAndName.size() == 2) {
329 // prefix of local module
330 if (identityPrefixAndName.get(0).equals(module.getPrefix())) {
331 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
332 identityPrefixAndName.get(1)));
335 // prefix of imported module
336 for (final ModuleImport moduleImport : module.getImports()) {
337 if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) {
338 final Module importedModule = schemaContext.findModule(moduleImport.getModuleName(),
339 moduleImport.getRevision()).get();
340 return findIdentitySchemaNodeInModule(importedModule, QName.create(
341 importedModule.getQNameModule(), identityPrefixAndName.get(1)));
345 throw new IllegalArgumentException(String.format("Cannot resolve prefix '%s' from identity '%s'.",
346 identityPrefixAndName.get(0), identity));
349 if (identityPrefixAndName.size() == 1) { // without prefix
350 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
351 identityPrefixAndName.get(0)));
354 throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
357 private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
358 for (final IdentitySchemaNode id : module.getIdentities()) {
359 if (identityQName.equals(id.getQName())) {
364 throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding"
365 + " identity schema node in the module %s.", identityQName, module));
368 private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path,
369 final NormalizedNodeContext currentNodeContext) {
370 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
371 final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
372 final List<PathArgument> pathArguments = path.getPathArguments();
373 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
374 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
375 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
376 if (possibleNode.isPresent()) {
377 return possibleNode.get();
384 private static NormalizedNode<?, ?> getNodeReferencedByLeafref(final RevisionAwareXPath xpath,
385 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
386 final TypedDataSchemaNode correspondingSchemaNode, final Object nodeValue) {
387 final NormalizedNode<?, ?> referencedNode = xpath.isAbsolute() ? getNodeReferencedByAbsoluteLeafref(xpath,
388 currentNodeContext, schemaContext, correspondingSchemaNode) : getNodeReferencedByRelativeLeafref(xpath,
389 currentNodeContext, schemaContext, correspondingSchemaNode);
391 if (referencedNode instanceof LeafSetNode) {
392 return getReferencedLeafSetEntryNode((LeafSetNode<?>) referencedNode, nodeValue);
395 if (referencedNode instanceof LeafNode && referencedNode.getValue().equals(nodeValue)) {
396 return referencedNode;
402 private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xpath,
403 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
404 final TypedDataSchemaNode correspondingSchemaNode) {
405 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
406 xpath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
407 final List<PathArgument> pathArguments = builder.build();
408 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
409 final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
410 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
411 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
412 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
413 if (possibleNode.isPresent()) {
414 return possibleNode.get();
421 private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xpath,
422 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
423 final TypedDataSchemaNode correspondingSchemaNode) {
424 NormalizedNodeContext relativeNodeContext = currentNodeContext;
425 final StringBuilder xPathStringBuilder = new StringBuilder(xpath.toString());
426 // strip the relative path of all ../ at the beginning
427 while (xPathStringBuilder.indexOf("../") == 0) {
428 xPathStringBuilder.delete(0, 3);
429 relativeNodeContext = relativeNodeContext.getParent();
432 // add / to the beginning of the path so that it can be processed the same way as an absolute path
433 xPathStringBuilder.insert(0, '/');
434 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
435 xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
436 final List<PathArgument> pathArguments = builder.build();
437 final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode();
438 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments);
439 if (possibleNode.isPresent()) {
440 return possibleNode.get();
446 private static LeafSetEntryNode<?> getReferencedLeafSetEntryNode(final LeafSetNode<?> referencedNode,
447 final Object currentNodeValue) {
448 for (final LeafSetEntryNode<?> entryNode : referencedNode.getValue()) {
449 if (currentNodeValue.equals(entryNode.getValue())) {
458 private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
459 for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
460 if (bitName.equals(bit.getName())) {
468 private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) {
469 for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) {
470 if (enumName.equals(enumPair.getName())) {
471 return enumPair.getValue();
475 throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
476 enumName, enumerationType));
479 private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) {
480 final ContextSupport contextSupport = normalizedNodeContext.getContextSupport();
481 Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s",
482 contextSupport.getClass());
483 return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext();
486 private static TypedDataSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext,
487 final NormalizedNodeContext currentNodeContext) {
488 Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator;
490 ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator()
491 .getAncestorOrSelfAxisIterator(currentNodeContext);
492 } catch (UnsupportedAxisException ex) {
493 throw new JaxenRuntimeException(ex);
496 final Deque<QName> schemaPathToCurrentNode = new ArrayDeque<>();
497 while (ancestorOrSelfAxisIterator.hasNext()) {
498 final NormalizedNode<?, ?> nextNode = ancestorOrSelfAxisIterator.next().getNode();
499 if (!(nextNode instanceof MapNode) && !(nextNode instanceof LeafSetNode)
500 && !(nextNode instanceof AugmentationNode)) {
501 schemaPathToCurrentNode.addFirst(nextNode.getNodeType());
505 final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode);
507 Preconditions.checkNotNull(schemaNode, "Node %s does not have a corresponding SchemaNode in the SchemaContext.",
508 currentNodeContext.getNode());
509 Preconditions.checkState(schemaNode instanceof TypedDataSchemaNode, "Node %s must be a leaf or a leaf-list.",
510 currentNodeContext.getNode());
511 return (TypedDataSchemaNode) schemaNode;