X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=netconf%2Fmdsal-netconf-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fmdsal%2Fconnector%2Fops%2Fget%2FFilterContentValidator.java;h=c6f5714e1f295403336ab0bac32f402b3d8ed1f5;hb=e0460d542f7088c543e51fd01917b3d360a1c1e2;hp=7e7a1573e577dd2f13d3db053e4f7c6cbe13bd3d;hpb=af130c5e03ebfae9f2682fe6c9bf2214d4419f9c;p=netconf.git diff --git a/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java b/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java index 7e7a1573e5..c6f5714e1f 100644 --- a/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java +++ b/netconf/mdsal-netconf-connector/src/main/java/org/opendaylight/netconf/mdsal/connector/ops/get/FilterContentValidator.java @@ -7,107 +7,127 @@ */ package org.opendaylight.netconf.mdsal.connector.ops.get; +import static org.opendaylight.yangtools.yang.data.util.ParserStreamUtils.findSchemaNodeByNameAndNamespace; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; import java.net.URI; import java.net.URISyntaxException; -import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.xml.namespace.NamespaceContext; +import javax.xml.stream.XMLStreamWriter; import org.opendaylight.controller.config.util.xml.DocumentedException; import org.opendaylight.controller.config.util.xml.MissingNameSpaceException; import org.opendaylight.controller.config.util.xml.XmlElement; import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext; import org.opendaylight.yangtools.yang.common.QName; import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.InstanceIdentifierBuilder; +import org.opendaylight.yangtools.yang.data.codec.xml.XmlCodecFactory; +import org.opendaylight.yangtools.yang.data.impl.codec.TypeDefinitionAwareCodec; +import org.opendaylight.yangtools.yang.data.util.codec.TypeAwareCodec; import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode; -import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode; -import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode; import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.type.IdentityrefTypeDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; /** * Class validates filter content against schema context. */ public class FilterContentValidator { + private static final Logger LOG = LoggerFactory.getLogger(FilterContentValidator.class); private final CurrentSchemaContext schemaContext; - /** - * @param schemaContext current schema context - */ - public FilterContentValidator(CurrentSchemaContext schemaContext) { + public FilterContentValidator(final CurrentSchemaContext schemaContext) { this.schemaContext = schemaContext; } /** - * Validates filter content against this validator schema context. If the filter is valid, method returns {@link YangInstanceIdentifier} - * of node which can be used as root for data selection. + * Validates filter content against this validator schema context. If the filter is valid, + * method returns {@link YangInstanceIdentifier} of node which can be used as root for data selection. + * * @param filterContent filter content * @return YangInstanceIdentifier - * @throws DocumentedException if filter content is not valid + * @throws DocumentedException if filter content validation failed */ - public YangInstanceIdentifier validate(XmlElement filterContent) throws DocumentedException { + public YangInstanceIdentifier validate(final XmlElement filterContent) throws DocumentedException { try { final URI namespace = new URI(filterContent.getNamespace()); final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(namespace, null); final DataSchemaNode schema = getRootDataSchemaNode(module, namespace, filterContent.getName()); - final FilterTree filterTree = validateNode(filterContent, schema, new FilterTree(schema.getQName(), Type.OTHER)); - return getFilterDataRoot(filterTree, YangInstanceIdentifier.builder()); - } catch (DocumentedException e) { - throw e; - } catch (Exception e) { + final FilterTree filterTree = validateNode( + filterContent, schema, new FilterTree(schema.getQName(), Type.OTHER, schema)); + return getFilterDataRoot(filterTree, filterContent, YangInstanceIdentifier.builder()); + } catch (final URISyntaxException e) { + throw new RuntimeException("Wrong namespace in element + " + filterContent.toString()); + } catch (final ValidationException e) { + LOG.debug("Filter content isn't valid", e); throw new DocumentedException("Validation failed. Cause: " + e.getMessage(), - DocumentedException.ErrorType.application, - DocumentedException.ErrorTag.unknown_namespace, - DocumentedException.ErrorSeverity.error); + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.UNKNOWN_NAMESPACE, + DocumentedException.ErrorSeverity.ERROR); } } /** - * Returns module's child data node of given name space and name - * @param module module + * Returns module's child data node of given name space and name. + * + * @param module module * @param nameSpace name space - * @param name name + * @param name name * @return child data node schema * @throws DocumentedException if child with given name is not present */ - private DataSchemaNode getRootDataSchemaNode(Module module, URI nameSpace, String name) throws DocumentedException { + private DataSchemaNode getRootDataSchemaNode(final Module module, final URI nameSpace, final String name) + throws DocumentedException { final Collection childNodes = module.getChildNodes(); - for (DataSchemaNode childNode : childNodes) { + for (final DataSchemaNode childNode : childNodes) { final QName qName = childNode.getQName(); if (qName.getNamespace().equals(nameSpace) && qName.getLocalName().equals(name)) { return childNode; } } - throw new DocumentedException("Unable to find node with namespace: " + nameSpace + "in schema context: " + schemaContext.getCurrentContext().toString(), - DocumentedException.ErrorType.application, - DocumentedException.ErrorTag.unknown_namespace, - DocumentedException.ErrorSeverity.error); + throw new DocumentedException("Unable to find node with namespace: " + nameSpace + + "in schema context: " + schemaContext.getCurrentContext().toString(), + DocumentedException.ErrorType.APPLICATION, + DocumentedException.ErrorTag.UNKNOWN_NAMESPACE, + DocumentedException.ErrorSeverity.ERROR); } /** * Recursively checks filter elements against the schema. Returns tree of nodes QNames as they appear in filter. - * @param element element to check + * + * @param element element to check * @param parentNodeSchema parent node schema - * @param tree parent node tree + * @param tree parent node tree * @return tree * @throws ValidationException if filter content is not valid */ - private FilterTree validateNode(XmlElement element, DataSchemaNode parentNodeSchema, FilterTree tree) throws ValidationException { + private FilterTree validateNode(final XmlElement element, final DataSchemaNode parentNodeSchema, + final FilterTree tree) throws ValidationException { final List childElements = element.getChildElements(); - for (XmlElement childElement : childElements) { + for (final XmlElement childElement : childElements) { try { - final Deque path = findSchemaNodeByNameAndNamespace(parentNodeSchema, childElement.getName(), new URI(childElement.getNamespace())); + final Deque path = findSchemaNodeByNameAndNamespace(parentNodeSchema, + childElement.getName(), new URI(childElement.getNamespace())); if (path.isEmpty()) { throw new ValidationException(element, childElement); } FilterTree subtree = tree; - for (DataSchemaNode dataSchemaNode : path) { - subtree = subtree.addChild(dataSchemaNode); + for (final DataSchemaNode dataSchemaNode : path) { + subtree = subtree.addChild(dataSchemaNode); } final DataSchemaNode childSchema = path.getLast(); validateNode(childElement, childSchema, subtree); @@ -120,14 +140,17 @@ public class FilterContentValidator { /** * Searches for YangInstanceIdentifier of node, which can be used as root for data selection. - * It goes as deep in tree as possible. Method stops traversing, when there are multiple child elements - * or when it encounters list node. - * @param tree QName tree - * @param builder builder - * @return YangInstanceIdentifier + * It goes as deep in tree as possible. Method stops traversing, when there are multiple child elements. If element + * represents list and child elements are key values, then it builds YangInstanceIdentifier of list entry. + * + * @param tree QName tree + * @param filterContent filter element + * @param builder builder @return YangInstanceIdentifier */ - private YangInstanceIdentifier getFilterDataRoot(FilterTree tree, YangInstanceIdentifier.InstanceIdentifierBuilder builder) { + private YangInstanceIdentifier getFilterDataRoot(FilterTree tree, final XmlElement filterContent, + final InstanceIdentifierBuilder builder) { builder.node(tree.getName()); + final List path = new ArrayList<>(); while (tree.getChildren().size() == 1) { final FilterTree child = tree.getChildren().iterator().next(); if (child.getType() == Type.CHOICE_CASE) { @@ -135,7 +158,9 @@ public class FilterContentValidator { continue; } builder.node(child.getName()); + path.add(child.getName().getLocalName()); if (child.getType() == Type.LIST) { + appendKeyIfPresent(child, filterContent, path, builder); return builder.build(); } tree = child; @@ -143,57 +168,67 @@ public class FilterContentValidator { return builder.build(); } - //FIXME this method will also be in yangtools ParserUtils, use that when https://git.opendaylight.org/gerrit/#/c/37031/ will be merged - /** - * Returns stack of schema nodes via which it was necessary to pass to get schema node with specified - * {@code childName} and {@code namespace} - * - * @param dataSchemaNode - * @param childName - * @param namespace - * @return stack of schema nodes via which it was passed through. If found schema node is direct child then stack - * contains only one node. If it is found under choice and case then stack should contains 2*n+1 element - * (where n is number of choices through it was passed) - */ - private Deque findSchemaNodeByNameAndNamespace(final DataSchemaNode dataSchemaNode, - final String childName, final URI namespace) { - final Deque result = new ArrayDeque<>(); - final List childChoices = new ArrayList<>(); - DataSchemaNode potentialChildNode = null; - if (dataSchemaNode instanceof DataNodeContainer) { - for (final DataSchemaNode childNode : ((DataNodeContainer) dataSchemaNode).getChildNodes()) { - if (childNode instanceof ChoiceSchemaNode) { - childChoices.add((ChoiceSchemaNode) childNode); - } else { - final QName childQName = childNode.getQName(); + private void appendKeyIfPresent(final FilterTree tree, final XmlElement filterContent, + final List pathToList, + final InstanceIdentifierBuilder builder) { + Preconditions.checkArgument(tree.getSchemaNode() instanceof ListSchemaNode); + final ListSchemaNode listSchemaNode = (ListSchemaNode) tree.getSchemaNode(); - if (childQName.getLocalName().equals(childName) && childQName.getNamespace().equals(namespace)) { - if (potentialChildNode == null || - childQName.getRevision().after(potentialChildNode.getQName().getRevision())) { - potentialChildNode = childNode; - } - } - } - } - } - if (potentialChildNode != null) { - result.push(potentialChildNode); - return result; + final Map map = getKeyValues(pathToList, filterContent, listSchemaNode); + if (!map.isEmpty()) { + builder.nodeWithKey(tree.getName(), map); } + } - // try to find data schema node in choice (looking for first match) - for (final ChoiceSchemaNode choiceNode : childChoices) { - for (final ChoiceCaseNode concreteCase : choiceNode.getCases()) { - final Deque resultFromRecursion = findSchemaNodeByNameAndNamespace(concreteCase, childName, - namespace); - if (!resultFromRecursion.isEmpty()) { - resultFromRecursion.push(concreteCase); - resultFromRecursion.push(choiceNode); - return resultFromRecursion; + private Map getKeyValues(final List path, final XmlElement filterContent, + final ListSchemaNode listSchemaNode) { + XmlElement current = filterContent; + //find list element + for (final String pathElement : path) { + final List childElements = current.getChildElements(pathElement); + // if there are multiple list entries present in the filter, we can't use any keys and must read whole list + if (childElements.size() != 1) { + return Collections.emptyMap(); + } + current = childElements.get(0); + } + final Map keys = new HashMap<>(); + final List keyDefinition = listSchemaNode.getKeyDefinition(); + for (final QName qualifiedName : keyDefinition) { + final Optional childElements = + current.getOnlyChildElementOptionally(qualifiedName.getLocalName()); + if (!childElements.isPresent()) { + return Collections.emptyMap(); + } + final Optional keyValue = childElements.get().getOnlyTextContentOptionally(); + if (keyValue.isPresent()) { + final LeafSchemaNode listKey = (LeafSchemaNode) listSchemaNode.getDataChildByName(qualifiedName); + if (listKey instanceof IdentityrefTypeDefinition) { + keys.put(qualifiedName, keyValue.get()); + } else { + if (listKey.getType() instanceof IdentityrefTypeDefinition) { + final Document document = filterContent.getDomElement().getOwnerDocument(); + final NamespaceContext nsContext = new UniversalNamespaceContextImpl(document, false); + final XmlCodecFactory xmlCodecFactory = + XmlCodecFactory.create(schemaContext.getCurrentContext()); + final TypeAwareCodec identityrefTypeCodec = + xmlCodecFactory.codecFor(listKey); + final QName deserializedKey = + (QName) identityrefTypeCodec.parseValue(nsContext, keyValue.get()); + keys.put(qualifiedName, deserializedKey); + } else { + final Object deserializedKey = TypeDefinitionAwareCodec.from(listKey.getType()) + .deserialize(keyValue.get()); + keys.put(qualifiedName, deserializedKey); + } } } } - return result; + return keys; + } + + private enum Type { + LIST, CHOICE_CASE, OTHER } /** @@ -203,16 +238,18 @@ public class FilterContentValidator { private final QName name; private final Type type; + private final DataSchemaNode schemaNode; private final Map children; - FilterTree(QName name, Type type) { + FilterTree(final QName name, final Type type, final DataSchemaNode schemaNode) { this.name = name; this.type = type; + this.schemaNode = schemaNode; this.children = new HashMap<>(); } - FilterTree addChild(DataSchemaNode data) { - Type type; + FilterTree addChild(final DataSchemaNode data) { + final Type type; if (data instanceof ChoiceCaseNode) { type = Type.CHOICE_CASE; } else if (data instanceof ListSchemaNode) { @@ -223,7 +260,7 @@ public class FilterContentValidator { final QName name = data.getQName(); FilterTree childTree = children.get(name); if (childTree == null) { - childTree = new FilterTree(name, type); + childTree = new FilterTree(name, type, data); } children.put(name, childTree); return childTree; @@ -240,14 +277,16 @@ public class FilterContentValidator { Type getType() { return type; } - } - private enum Type { - LIST, CHOICE_CASE, OTHER + DataSchemaNode getSchemaNode() { + return schemaNode; + } } private static class ValidationException extends Exception { - public ValidationException(XmlElement parent, XmlElement child) { + private static final long serialVersionUID = 1L; + + ValidationException(final XmlElement parent, final XmlElement child) { super("Element " + child + " can't be child of " + parent); } }