/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.yang.data.jaxen; import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; import com.google.common.base.Verify; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.jaxen.ContextSupport; import org.jaxen.Function; import org.jaxen.FunctionCallException; import org.jaxen.FunctionContext; import org.jaxen.JaxenRuntimeException; import org.jaxen.UnresolvableException; import org.jaxen.UnsupportedAxisException; import org.jaxen.XPathFunctionContext; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument; import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode; import org.opendaylight.yangtools.yang.data.api.schema.LeafSetNode; import org.opendaylight.yangtools.yang.data.api.schema.MapNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNodes; import org.opendaylight.yangtools.yang.model.api.IdentitySchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; import org.opendaylight.yangtools.yang.model.api.ModuleImport; import org.opendaylight.yangtools.yang.model.api.RevisionAwareXPath; import org.opendaylight.yangtools.yang.model.api.SchemaContext; import org.opendaylight.yangtools.yang.model.api.SchemaNode; import org.opendaylight.yangtools.yang.model.api.TypeDefinition; import org.opendaylight.yangtools.yang.model.api.TypedSchemaNode; import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.EnumTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.InstanceIdentifierTypeDefinition; import org.opendaylight.yangtools.yang.model.api.type.LeafrefTypeDefinition; import org.opendaylight.yangtools.yang.model.util.RegexUtils; import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil; /** * A {@link FunctionContext} which contains also YANG-specific functions current(), re-match(), deref(), * derived-from(), derived-from-or-self(), enum-value() and bit-is-set(). */ final class YangFunctionContext implements FunctionContext { private static final Splitter COLON_SPLITTER = Splitter.on(':'); private static final Double DOUBLE_NAN = Double.NaN; // Core XPath functions, as per http://tools.ietf.org/html/rfc6020#section-6.4.1 private static final FunctionContext XPATH_FUNCTION_CONTEXT = new XPathFunctionContext(false); // current() function, as per http://tools.ietf.org/html/rfc6020#section-6.4.1 private static final Function CURRENT_FUNCTION = (context, args) -> { if (!args.isEmpty()) { throw new FunctionCallException("current() takes no arguments."); } Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass()); return ((NormalizedNodeContext) context); }; // re-match(string subject, string pattern) function as per https://tools.ietf.org/html/rfc7950#section-10.2.1 private static final Function REMATCH_FUNCTION = (context, args) -> { if (args == null || args.size() != 2) { throw new FunctionCallException("re-match() takes two arguments: string subject, string pattern."); } if (!(args.get(0) instanceof String)) { throw new FunctionCallException("First argument of re-match() should be a String."); } if (!(args.get(1) instanceof String)) { throw new FunctionCallException("Second argument of re-match() should be a String."); } final String subject = (String) args.get(0); final String rawPattern = (String) args.get(1); final String pattern = RegexUtils.getJavaRegexFromXSD(rawPattern); return (Boolean) subject.matches(pattern); }; // deref(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.3.1 private static final Function DEREF_FUNCTION = (context, args) -> { if (!args.isEmpty()) { throw new FunctionCallException("deref() takes only one argument: node-set nodes."); } Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass()); final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context; final SchemaContext schemaContext = getSchemaContext(currentNodeContext); final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext); final Object nodeValue = currentNodeContext.getNode().getValue(); if (correspondingSchemaNode.getType() instanceof InstanceIdentifierTypeDefinition && nodeValue instanceof YangInstanceIdentifier) { return getNodeReferencedByInstanceIdentifier((YangInstanceIdentifier) nodeValue, currentNodeContext); } if (correspondingSchemaNode.getType() instanceof LeafrefTypeDefinition) { final LeafrefTypeDefinition leafrefType = (LeafrefTypeDefinition) correspondingSchemaNode.getType(); final RevisionAwareXPath xPath = leafrefType.getPathStatement(); return getNodeReferencedByLeafref(xPath, currentNodeContext, schemaContext, correspondingSchemaNode, nodeValue); } return null; }; private static NormalizedNode<?, ?> getNodeReferencedByInstanceIdentifier(final YangInstanceIdentifier path, final NormalizedNodeContext currentNodeContext) { final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator(); final NormalizedNode<?, ?> rootNode = navigator.getRootNode(); final List<PathArgument> pathArguments = path.getPathArguments(); if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) { final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size()); final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath); if (possibleNode.isPresent()) { return possibleNode.get(); } } return null; } private static NormalizedNode<?, ?> getNodeReferencedByLeafref(final RevisionAwareXPath xPath, final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode, final Object nodeValue) { final NormalizedNode<?, ?> referencedNode = xPath.isAbsolute() ? getNodeReferencedByAbsoluteLeafref(xPath, currentNodeContext, schemaContext, correspondingSchemaNode) : getNodeReferencedByRelativeLeafref(xPath, currentNodeContext, schemaContext, correspondingSchemaNode); if (referencedNode instanceof LeafSetNode) { return getReferencedLeafSetEntryNode((LeafSetNode<?>) referencedNode, nodeValue); } if (referencedNode instanceof LeafNode && referencedNode.getValue().equals(nodeValue)) { return referencedNode; } return null; } private static NormalizedNode<?, ?> getNodeReferencedByAbsoluteLeafref(final RevisionAwareXPath xPath, final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode) { final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder( xPath.toString(), schemaContext, correspondingSchemaNode, currentNodeContext); final List<PathArgument> pathArguments = builder.build(); final NormalizedNodeNavigator navigator = (NormalizedNodeNavigator) currentNodeContext.getNavigator(); final NormalizedNode<?, ?> rootNode = navigator.getRootNode(); if (pathArguments.get(0).getNodeType().equals(rootNode.getNodeType())) { final List<PathArgument> relPath = pathArguments.subList(1, pathArguments.size()); final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(rootNode, relPath); if (possibleNode.isPresent()) { return possibleNode.get(); } } return null; } private static NormalizedNode<?, ?> getNodeReferencedByRelativeLeafref(final RevisionAwareXPath xPath, final NormalizedNodeContext currentNodeContext, final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode) { NormalizedNodeContext relativeNodeContext = currentNodeContext; final StringBuilder xPathStringBuilder = new StringBuilder(xPath.toString()); // strip the relative path of all ../ at the beginning while (xPathStringBuilder.indexOf("../") == 0) { xPathStringBuilder.delete(0, 3); relativeNodeContext = relativeNodeContext.getParent(); } // add / to the beginning of the path so that it can be processed the same way as an absolute path xPathStringBuilder.insert(0, '/'); final LeafrefXPathStringParsingPathArgumentBuilder builder = new LeafrefXPathStringParsingPathArgumentBuilder( xPathStringBuilder.toString(), schemaContext, correspondingSchemaNode, currentNodeContext); final List<PathArgument> pathArguments = builder.build(); final NormalizedNode<?, ?> relativeNode = relativeNodeContext.getNode(); final Optional<NormalizedNode<?, ?>> possibleNode = NormalizedNodes.findNode(relativeNode, pathArguments); if (possibleNode.isPresent()) { return possibleNode.get(); } return null; } private static LeafSetEntryNode<?> getReferencedLeafSetEntryNode(final LeafSetNode<?> referencedNode, final Object currentNodeValue) { for (final LeafSetEntryNode<?> entryNode : referencedNode.getValue()) { if (currentNodeValue.equals(entryNode.getValue())) { return entryNode; } } return null; } // derived-from(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.1 private static final Function DERIVED_FROM_FUNCTION = (context, args) -> { if (args == null || args.size() != 1) { throw new FunctionCallException("derived-from() takes two arguments: node-set nodes, string identity."); } if (!(args.get(0) instanceof String)) { throw new FunctionCallException("Argument 'identity' of derived-from() function should be a String."); } final String identityArg = (String) args.get(0); Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass()); final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context; final SchemaContext schemaContext = getSchemaContext(currentNodeContext); final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext); if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) { return Boolean.FALSE; } if (!(currentNodeContext.getNode().getValue() instanceof QName)) { return Boolean.FALSE; } final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue(); final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext, correspondingSchemaNode); final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue, schemaContext); final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>(); collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities); return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode)); }; // derived-from-or-self(node-set nodes, string identity) function as per https://tools.ietf.org/html/rfc7950#section-10.4.2 private static final Function DERIVED_FROM_OR_SELF_FUNCTION = (context, args) -> { if (args == null || args.size() != 1) { throw new FunctionCallException("derived-from-or-self() takes two arguments: node-set nodes, string identity"); } if (!(args.get(0) instanceof String)) { throw new FunctionCallException("Argument 'identity' of derived-from-or-self() function should be a String."); } final String identityArg = (String) args.get(0); Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass()); final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context; final SchemaContext schemaContext = getSchemaContext(currentNodeContext); final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext); if (!(correspondingSchemaNode.getType() instanceof IdentityrefTypeDefinition)) { return Boolean.FALSE; } if (!(currentNodeContext.getNode().getValue() instanceof QName)) { return Boolean.FALSE; } final QName currentNodeValue = (QName) currentNodeContext.getNode().getValue(); final IdentitySchemaNode identityArgSchemaNode = getIdentitySchemaNodeFromString(identityArg, schemaContext, correspondingSchemaNode); final IdentitySchemaNode currentNodeIdentitySchemaNode = getIdentitySchemaNodeFromQName(currentNodeValue, schemaContext); if (currentNodeIdentitySchemaNode.equals(identityArgSchemaNode)) { return Boolean.TRUE; } final Set<IdentitySchemaNode> ancestorIdentities = new HashSet<>(); collectAncestorIdentities(currentNodeIdentitySchemaNode, ancestorIdentities); return Boolean.valueOf(ancestorIdentities.contains(identityArgSchemaNode)); }; private static void collectAncestorIdentities(final IdentitySchemaNode identity, final Set<IdentitySchemaNode> ancestorIdentities) { for (final IdentitySchemaNode id : identity.getBaseIdentities()) { collectAncestorIdentities(id, ancestorIdentities); ancestorIdentities.add(id); } } private static IdentitySchemaNode getIdentitySchemaNodeFromQName(final QName identityQName, final SchemaContext schemaContext) { final Module module = schemaContext.findModuleByNamespaceAndRevision(identityQName.getNamespace(), identityQName.getRevision()); return findIdentitySchemaNodeInModule(module, identityQName); } private static IdentitySchemaNode getIdentitySchemaNodeFromString(final String identity, final SchemaContext schemaContext, final TypedSchemaNode correspondingSchemaNode) { final List<String> identityPrefixAndName = COLON_SPLITTER.splitToList(identity); final Module module = schemaContext.findModuleByNamespaceAndRevision( correspondingSchemaNode.getQName().getNamespace(), correspondingSchemaNode.getQName().getRevision()); if (identityPrefixAndName.size() == 2) { // prefix of local module if (identityPrefixAndName.get(0).equals(module.getPrefix())) { return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(), identityPrefixAndName.get(1))); } // prefix of imported module for (final ModuleImport moduleImport : module.getImports()) { if (identityPrefixAndName.get(0).equals(moduleImport.getPrefix())) { final Module importedModule = schemaContext.findModuleByName(moduleImport.getModuleName(), moduleImport.getRevision()); return findIdentitySchemaNodeInModule(importedModule, QName.create( importedModule.getQNameModule(), identityPrefixAndName.get(1))); } } throw new IllegalArgumentException(String.format("Cannot resolve prefix '%s' from identity '%s'.", identityPrefixAndName.get(0), identity)); } if (identityPrefixAndName.size() == 1) { // without prefix return findIdentitySchemaNodeInModule(module, QName.create(module.getQNameModule(), identityPrefixAndName.get(0))); } throw new IllegalArgumentException(String.format("Malformed identity argument: %s.", identity)); } private static IdentitySchemaNode findIdentitySchemaNodeInModule(final Module module, final QName identityQName) { for (final IdentitySchemaNode id : module.getIdentities()) { if (identityQName.equals(id.getQName())) { return id; } } throw new IllegalArgumentException(String.format("Identity %s does not have a corresponding" + " identity schema node in the module %s.", identityQName, module)); } // enum-value(node-set nodes) function as per https://tools.ietf.org/html/rfc7950#section-10.5.1 private static final Function ENUM_VALUE_FUNCTION = (context, args) -> { if (!args.isEmpty()) { throw new FunctionCallException("enum-value() takes one argument: node-set nodes."); } Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass()); final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context; final SchemaContext schemaContext = getSchemaContext(currentNodeContext); final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext); final TypeDefinition<?> nodeType = correspondingSchemaNode.getType(); if (!(nodeType instanceof EnumTypeDefinition)) { return DOUBLE_NAN; } final Object nodeValue = currentNodeContext.getNode().getValue(); if (!(nodeValue instanceof String)) { return DOUBLE_NAN; } final EnumTypeDefinition enumerationType = (EnumTypeDefinition) nodeType; final String enumName = (String) nodeValue; return getEnumValue(enumerationType, enumName); }; private static int getEnumValue(final EnumTypeDefinition enumerationType, final String enumName) { for (final EnumTypeDefinition.EnumPair enumPair : enumerationType.getValues()) { if (enumName.equals(enumPair.getName())) { return enumPair.getValue(); } } throw new IllegalStateException(String.format("Enum %s does not belong to enumeration %s.", enumName, enumerationType)); } // bit-is-set(node-set nodes, string bit-name) function as per https://tools.ietf.org/html/rfc7950#section-10.6.1 private static final Function BIT_IS_SET_FUNCTION = (context, args) -> { if (args == null || args.size() != 1) { throw new FunctionCallException("bit-is-set() takes two arguments: node-set nodes, string bit-name"); } if (!(args.get(0) instanceof String)) { throw new FunctionCallException("Argument bit-name of bit-is-set() function should be a String"); } final String bitName = (String) args.get(0); Verify.verify(context instanceof NormalizedNodeContext, "Unhandled context %s", context.getClass()); final NormalizedNodeContext currentNodeContext = (NormalizedNodeContext) context; final SchemaContext schemaContext = getSchemaContext(currentNodeContext); final TypedSchemaNode correspondingSchemaNode = getCorrespondingTypedSchemaNode(schemaContext, currentNodeContext); final TypeDefinition<?> nodeType = correspondingSchemaNode.getType(); if (!(nodeType instanceof BitsTypeDefinition)) { return Boolean.FALSE; } final Object nodeValue = currentNodeContext.getNode().getValue(); if (!(nodeValue instanceof Set)) { return Boolean.FALSE; } final BitsTypeDefinition bitsType = (BitsTypeDefinition) nodeType; Preconditions.checkState(containsBit(bitsType, bitName), "Bit %s does not belong to bits %s.", bitName, bitsType); return Boolean.valueOf(((Set<?>)nodeValue).contains(bitName)); }; private static boolean containsBit(final BitsTypeDefinition bitsType, final String bitName) { for (BitsTypeDefinition.Bit bit : bitsType.getBits()) { if (bitName.equals(bit.getName())) { return true; } } return false; } private static SchemaContext getSchemaContext(final NormalizedNodeContext normalizedNodeContext) { final ContextSupport contextSupport = normalizedNodeContext.getContextSupport(); Verify.verify(contextSupport instanceof NormalizedNodeContextSupport, "Unhandled context support %s", contextSupport.getClass()); return ((NormalizedNodeContextSupport) contextSupport).getSchemaContext(); } private static TypedSchemaNode getCorrespondingTypedSchemaNode(final SchemaContext schemaContext, final NormalizedNodeContext currentNodeContext) { Iterator<NormalizedNodeContext> ancestorOrSelfAxisIterator; try { ancestorOrSelfAxisIterator = currentNodeContext.getContextSupport().getNavigator() .getAncestorOrSelfAxisIterator(currentNodeContext); } catch (UnsupportedAxisException ex) { throw new JaxenRuntimeException(ex); } final Deque<QName> schemaPathToCurrentNode = new ArrayDeque<>(); while (ancestorOrSelfAxisIterator.hasNext()) { final NormalizedNode<?, ?> nextNode = ancestorOrSelfAxisIterator.next().getNode(); if (!(nextNode instanceof MapNode) && !(nextNode instanceof LeafSetNode) && !(nextNode instanceof AugmentationNode)) { schemaPathToCurrentNode.addFirst(nextNode.getNodeType()); } } final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(schemaContext, schemaPathToCurrentNode); Preconditions.checkNotNull(schemaNode, "Node %s does not have a corresponding SchemaNode in the SchemaContext.", currentNodeContext.getNode()); Preconditions.checkState(schemaNode instanceof TypedSchemaNode, "Node %s must be a leaf or a leaf-list.", currentNodeContext.getNode()); return (TypedSchemaNode) schemaNode; } // Singleton instance of reuse private static final YangFunctionContext INSTANCE = new YangFunctionContext(); private YangFunctionContext() { } static YangFunctionContext getInstance() { return INSTANCE; } @Override public Function getFunction(final String namespaceURI, final String prefix, final String localName) throws UnresolvableException { if (prefix == null) { switch (localName) { case "bit-is-set": return BIT_IS_SET_FUNCTION; case "current": return CURRENT_FUNCTION; case "deref": return DEREF_FUNCTION; case "derived-from": return DERIVED_FROM_FUNCTION; case "derived-from-or-self": return DERIVED_FROM_OR_SELF_FUNCTION; case "enum-value": return ENUM_VALUE_FUNCTION; case "re-match": return REMATCH_FUNCTION; } } return XPATH_FUNCTION_CONTEXT.getFunction(namespaceURI, prefix, localName); } }