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.Iterator;
16 import java.util.List;
17 import java.util.Optional;
19 import org.jaxen.Context;
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.TypedDataSchemaNode;
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);
65 // Singleton instance of reuse
66 private static final YangFunctionContext INSTANCE = new YangFunctionContext();
68 private YangFunctionContext() {
71 static YangFunctionContext getInstance() {
76 public Function getFunction(final String namespaceURI, final String prefix, final String localName)
77 throws UnresolvableException {
81 return YangFunctionContext::bitIsSet;
83 return YangFunctionContext::current;
85 return YangFunctionContext::deref;
87 return YangFunctionContext::derivedFrom;
88 case "derived-from-or-self":
89 return YangFunctionContext::derivedFromOrSelf;
91 return YangFunctionContext::enumValueFunction;
93 return YangFunctionContext::reMatch;
99 return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName);
102 // bit-is-set(node-set nodes, string bit-name) function as per
103 // https://tools.ietf.org/html/rfc7950#section-10.6.1
104 private static boolean bitIsSet(final Context context, final List<?> args) throws FunctionCallException {
105 if (args == null || args.size() != 1) {
106 throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name");
109 if (!(args.get(0) instanceof String)) {
110 throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String");
113 final String bitName = (String) args.get(0);
115 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
117 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
118 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
119 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
122 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
123 if (!(nodeType instanceof BitsTypeDefinition)) {
127 final Object nodeValue = currentNodeContext.getNode().getValue();
128 if (!(nodeValue instanceof Set)) {
132 final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType;
133 Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName,
135 return ((Set<?>)nodeValue).contains(bitName);
138 // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1
139 private static NormalizedNodeContext current(final Context context, final List<?> args)
140 throws FunctionCallException {
141 if (!args.isEmpty()) {
142 throw new FunctionCallException("current() takes no arguments.");
145 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
146 return (NormalizedNodeContext) context;
149 // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1
150 private static NormalizedNode<?, ?> deref(final Context context, final List<?> args) throws FunctionCallException {
151 if (!args.isEmpty()) {
152 throw new FunctionCallException("deref() takes only one argument: node-set nodes.");
155 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
156 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
157 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
158 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
161 final Object nodeValue = currentNodeContext.getNode().getValue();
162 final TypeDefinition<?> type = correspondingSchemaNode.getType();
163 if (type instanceof InstanceIdentifierTypeDefinition) {
164 return nodeValue instanceof YangInstanceIdentifier
165 ? getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext)
168 if (type instanceof LeafrefTypeDefinition) {
169 final RevisionAwareXPath xpath = ((LeafrefTypeDefinition) type).getPathStatement();
170 return getNodeReferencedByLeafref(xpath, currentNodeContext, schemaContext, correspondingSchemaNode,
176 // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1
177 private static boolean derivedFrom(final Context context, final List<?> args) throws FunctionCallException {
178 if (args == null || args.size() != 1) {
179 throw new FunctionCallException("derived-from() takes two arguments: node-set nodes, string identity.");
182 if (!(args.get(0) instanceof String)) {
183 throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String.");
186 final String identityArg = (String) args.get(0);
188 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
190 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
191 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
192 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
195 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
199 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
203 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
205 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
206 correspondingSchemaNode);
207 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
209 return isAncestorOf(identityArgSchemaNode, currentNodeIdentitySchemaNode);
212 // derived-from-or-self(node-set nodes, string identity) function as per
213 // https://tools.ietf.org/html/rfc7950#section-10.4.2
214 private static boolean derivedFromOrSelf(final Context context, final List<?> args) throws FunctionCallException {
215 if (args == null || args.size() != 1) {
216 throw new FunctionCallException(
217 "derived-from-or-self() takes two arguments: node-set nodes, string identity");
220 if (!(args.get(0) instanceof String)) {
221 throw new FunctionCallException(
222 "Argument 'identity' of derived-from-or-self() 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 TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
234 if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) {
238 if (!(currentNodeContext.getNode().getValue() instanceof QName)) {
242 final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue();
244 final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext,
245 correspondingSchemaNode);
246 final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue,
248 return currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)
249 || isAncestorOf(identityArgSchemaNode, currentNodeIdentitySchemaNode);
252 // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1
253 private static Object enumValueFunction(final Context context, final List<?> args) throws FunctionCallException {
254 if (!args.isEmpty()) {
255 throw new FunctionCallException("enum-value() takes one argument: node-set nodes.");
258 Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass());
260 final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context;
261 final SchemaContext schemaContext = getSchemaContext(currentNodeContext);
262 final TypedDataSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext,
265 final TypeDefinition<?> nodeType = correspondingSchemaNode.getType();
266 if (!(nodeType instanceof EnumTypeDefinition)) {
270 final Object nodeValue = currentNodeContext.getNode().getValue();
271 if (!(nodeValue instanceof String)) {
275 final EnumTypeDefinition enumerationType = (EnumTypeDefinition) nodeType;
276 final String enumName = (String) nodeValue;
278 return getEnumValue(enumerationType, enumName);
281 // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1
282 private static boolean reMatch(final Context context, final List<?> args) throws FunctionCallException {
283 if (args == null || args.size() != 2) {
284 throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern.");
286 final Object subject = args.get(0);
287 if (!(subject instanceof String)) {
288 throw new FunctionCallException("First argument of re-match() should be a String.");
290 final Object pattern = args.get(1);
291 if (!(pattern instanceof String)) {
292 throw new FunctionCallException("Second argument of re-match() should be a String.");
295 return ((String) subject).matches(RegexUtils.getJavaRegexFromXSD((String) pattern));
298 private static boolean isAncestorOf(final IdentitySchemaNode identity, final IdentitySchemaNode descendant) {
299 for (IdentitySchemaNode base : descendant.getBaseIdentities()) {
300 if (identity.equals(base) || isAncestorOf(identity, base)) {
307 private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName,
308 final SchemaContext schemaContext) {
309 final Optional<Module> module = schemaContext.findModule(identityQName.getModule());
310 Preconditions.checkArgument(module.isPresent(), "Module for %s not found", identityQName);
311 return findIdentitySchemaNodeInModule(module.get(), identityQName);
314 private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity,
315 final SchemaContext schemaContext, final TypedDataSchemaNode correspondingSchemaNode) {
316 final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity);
317 final Module module = schemaContext.findModule(correspondingSchemaNode.getQName().getModule()).get();
318 if (identityPrefixAndName.size() == 2) {
319 // prefix of local module
320 if (identityPrefixAndName.get(0).equals(module.getPrefix())) {
321 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
322 identityPrefixAndName.get(1)));
325 // prefix of imported module
326 for (final ModuleImport moduleImport : module.getImports()) {
327 if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) {
328 final Module importedModule = schemaContext.findModule(moduleImport.getModuleName(),
329 moduleImport.getRevision()).get();
330 return findIdentitySchemaNodeInModule(importedModule, QName.create(
331 importedModule.getQNameModule(), identityPrefixAndName.get(1)));
335 throw new IllegalArgumentException(String.format("Cannot resolve prefix '%s' from identity '%s'.",
336 identityPrefixAndName.get(0), identity));
339 if (identityPrefixAndName.size() == 1) {
341 return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(),
342 identityPrefixAndName.get(0)));
345 throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity));
348 private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) {
349 for (final IdentitySchemaNode id : module.getIdentities()) {
350 if (identityQName.equals(id.getQName())) {
355 throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding"
356 + " identity schema node in the module %s.", identityQName, module));
359 private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path,
360 final NormalizedNodeContext currentNodeContext) {
361 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
362 final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
363 final List<PathArgument> pathArguments = path.getPathArguments();
364 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
365 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
366 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
367 if (possibleNode.isPresent()) {
368 return possibleNode.get();
375 private static NormalizedNode<?, ?> getNodeReferencedByLeafref(final RevisionAwareXPath xpath,
376 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
377 final TypedDataSchemaNode correspondingSchemaNode, final Object nodeValue) {
378 final NormalizedNode<?, ?> referencedNode = xpath.isAbsolute() ? getNodeReferencedByAbsoluteLeafref(xpath,
379 currentNodeContext, schemaContext, correspondingSchemaNode) : getNodeReferencedByRelativeLeafref(xpath,
380 currentNodeContext, schemaContext, correspondingSchemaNode);
382 if (referencedNode instanceof LeafSetNode) {
383 return getReferencedLeafSetEntryNode((LeafSetNode<?>) referencedNode, nodeValue);
386 if (referencedNode instanceof LeafNode && referencedNode.getValue().equals(nodeValue)) {
387 return referencedNode;
393 private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xpath,
394 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
395 final TypedDataSchemaNode correspondingSchemaNode) {
396 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
397 xpath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
398 final List<PathArgument> pathArguments = builder.build();
399 final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator();
400 final NormalizedNode<?, ?> rootNode = navigator.getRootNode();
401 if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) {
402 final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size());
403 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath);
404 if (possibleNode.isPresent()) {
405 return possibleNode.get();
412 private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xpath,
413 final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext,
414 final TypedDataSchemaNode correspondingSchemaNode) {
415 NormalizedNodeContext relativeNodeContext = currentNodeContext;
416 final StringBuilder xPathStringBuilder = new StringBuilder(xpath.toString());
417 // strip the relative path of all ../ at the beginning
418 while (xPathStringBuilder.indexOf("../") == 0) {
419 xPathStringBuilder.delete(0, 3);
420 relativeNodeContext = relativeNodeContext.getParent();
423 // add / to the beginning of the path so that it can be processed the same way as an absolute path
424 xPathStringBuilder.insert(0, '/');
425 final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder(
426 xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext);
427 final List<PathArgument> pathArguments = builder.build();
428 final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode();
429 final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments);
430 if (possibleNode.isPresent()) {
431 return possibleNode.get();
437 private static LeafSetEntryNode<?> getReferencedLeafSetEntryNode(final LeafSetNode<?> referencedNode,
438 final Object currentNodeValue) {
439 for (final LeafSetEntryNode<?> entryNode : referencedNode.getValue()) {
440 if (currentNodeValue.equals(entryNode.getValue())) {
449 private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) {
450 for (BitsTypeDefinition.Bit bit : bitsType.getBits()) {
451 if (bitName.equals(bit.getName())) {
459 private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) {
460 for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) {
461 if (enumName.equals(enumPair.getName())) {
462 return enumPair.getValue();
466 throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.",
467 enumName, enumerationType));
470 private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) {
471 final ContextSupport contextSupport = normalizedNodeContext.getContextSupport();
472 Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s",
473 contextSupport.getClass());
474 return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext();
477 private static TypedDataSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext,
478 final NormalizedNodeContext currentNodeContext) {
479 Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator;
481 ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator()
482 .getAncestorOrSelfAxisIterator(currentNodeContext);
483 } catch (UnsupportedAxisException ex) {
484 throw new JaxenRuntimeException(ex);
487 final Deque<QName> schemaPathToCurrentNode = new ArrayDeque<>();
488 while (ancestorOrSelfAxisIterator.hasNext()) {
489 final NormalizedNode<?, ?> nextNode = ancestorOrSelfAxisIterator.next().getNode();
490 if (!(nextNode instanceof MapNode) && !(nextNode instanceof LeafSetNode)
491 && !(nextNode instanceof AugmentationNode)) {
492 schemaPathToCurrentNode.addFirst(nextNode.getNodeType());
496 final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode);
498 Preconditions.checkNotNull(schemaNode, "Node %s does not have a corresponding SchemaNode in the SchemaContext.",
499 currentNodeContext.getNode());
500 Preconditions.checkState(schemaNode instanceof TypedDataSchemaNode, "Node %s must be a leaf or a leaf-list.",
501 currentNodeContext.getNode());
502 return (TypedDataSchemaNode) schemaNode;