X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;f=restconf%2Fsal-rest-connector%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fnetconf%2Fsal%2Frest%2Fimpl%2FXmlToPATCHBodyReader.java;h=fc8ad710e73ba7d2fb0d6dc5d62d26dc9dd8246a;hb=741e94d72133150cac203bb5ecedf787203ef795;hp=fe63f3137338e0d6a8ca1b0d85c1bc919178838b;hpb=554c48aa39c535e9cf34c57198d679bd138adad2;p=netconf.git diff --git a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java index fe63f31373..fc8ad710e7 100644 --- a/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java +++ b/restconf/sal-rest-connector/src/main/java/org/opendaylight/netconf/sal/rest/impl/XmlToPATCHBodyReader.java @@ -8,83 +8,76 @@ package org.opendaylight.netconf.sal.rest.impl; +import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.net.URI; import java.util.ArrayList; -import java.util.Collections; +import java.util.Iterator; import java.util.List; +import javax.annotation.Nonnull; import javax.ws.rs.Consumes; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.Provider; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; -import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier; -import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode; -import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; -import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes; -import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; -import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.NodeList; -import org.opendaylight.netconf.sal.rest.api.Draft02.MediaTypes; +import org.opendaylight.netconf.sal.rest.api.Draft02; import org.opendaylight.netconf.sal.rest.api.RestconfService; import org.opendaylight.netconf.sal.restconf.impl.InstanceIdentifierContext; import org.opendaylight.netconf.sal.restconf.impl.PATCHContext; +import org.opendaylight.netconf.sal.restconf.impl.PATCHEditOperation; +import org.opendaylight.netconf.sal.restconf.impl.PATCHEntity; import org.opendaylight.netconf.sal.restconf.impl.RestconfDocumentedException; import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag; import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType; +import org.opendaylight.yangtools.util.xml.UntrustedXML; +import org.opendaylight.yangtools.yang.common.QName; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier; +import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates; +import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode; import org.opendaylight.yangtools.yang.data.impl.codec.xml.XmlUtils; import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory; +import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode; import org.opendaylight.yangtools.yang.model.api.DataNodeContainer; import org.opendaylight.yangtools.yang.model.api.DataSchemaNode; +import org.opendaylight.yangtools.yang.model.api.ListSchemaNode; +import org.opendaylight.yangtools.yang.model.api.Module; +import org.opendaylight.yangtools.yang.model.api.SchemaNode; +import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +/** + * @deprecated This class will be replaced by + * {@link org.opendaylight.restconf.jersey.providers.XmlToPATCHBodyReader} + */ +@Deprecated @Provider -@Consumes({MediaTypes.PATCH + RestconfService.XML}) +@Consumes({Draft02.MediaTypes.PATCH + RestconfService.XML}) public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader { private final static Logger LOG = LoggerFactory.getLogger(XmlToPATCHBodyReader.class); - private static final DocumentBuilderFactory BUILDERFACTORY; - - static { - final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - try { - factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); - factory.setFeature("http://xml.org/sax/features/external-general-entities", false); - factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - factory.setXIncludeAware(false); - factory.setExpandEntityReferences(false); - } catch (final ParserConfigurationException e) { - throw new ExceptionInInitializerError(e); - } - factory.setNamespaceAware(true); - factory.setCoalescing(true); - factory.setIgnoringElementContentWhitespace(true); - factory.setIgnoringComments(true); - BUILDERFACTORY = factory; - } @Override - public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { + public boolean isReadable(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType) { return true; } @Override - public PATCHContext readFrom(Class type, Type genericType, Annotation[] annotations, MediaType - mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, - WebApplicationException { + public PATCHContext readFrom(final Class type, final Type genericType, + final Annotation[] annotations, final MediaType mediaType, + final MultivaluedMap httpHeaders, final InputStream entityStream) + throws IOException, WebApplicationException { try { final InstanceIdentifierContext path = getInstanceIdentifierContext(); @@ -94,14 +87,7 @@ public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider i return new PATCHContext(path, null, null); } - final DocumentBuilder dBuilder; - try { - dBuilder = BUILDERFACTORY.newDocumentBuilder(); - } catch (final ParserConfigurationException e) { - throw new IllegalStateException("Failed to parse XML document", e); - } - final Document doc = dBuilder.parse(entityStream); - + final Document doc = UntrustedXML.newDocumentBuilder().parse(entityStream); return parse(path, doc); } catch (final RestconfDocumentedException e) { throw e; @@ -113,51 +99,186 @@ public class XmlToPATCHBodyReader extends AbstractIdentifierAwareJaxRsProvider i } } - private PATCHContext parse(final InstanceIdentifierContext pathContext, final Document doc) { + private static PATCHContext parse(final InstanceIdentifierContext pathContext, final Document doc) { final List resultCollection = new ArrayList<>(); final String patchId = doc.getElementsByTagName("patch-id").item(0).getFirstChild().getNodeValue(); final NodeList editNodes = doc.getElementsByTagName("edit"); - final DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode(); final DomToNormalizedNodeParserFactory parserFactory = DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, pathContext.getSchemaContext()); for (int i = 0; i < editNodes.getLength(); i++) { - Element element = (Element) editNodes.item(i); + DataSchemaNode schemaNode = (DataSchemaNode) pathContext.getSchemaNode(); + final Element element = (Element) editNodes.item(i); final String operation = element.getElementsByTagName("operation").item(0).getFirstChild().getNodeValue(); final String editId = element.getElementsByTagName("edit-id").item(0).getFirstChild().getNodeValue(); final String target = element.getElementsByTagName("target").item(0).getFirstChild().getNodeValue(); - DataSchemaNode targetNode = ((DataNodeContainer)(pathContext.getSchemaNode())).getDataChildByName - (target.replace("/", "")); + final List values = readValueNodes(element, operation); + final Element firstValueElement = values != null ? values.get(0) : null; + + // get namespace according to schema node from path context or value + final String namespace = (firstValueElement == null) ? + schemaNode.getQName().getNamespace().toString() : firstValueElement.getNamespaceURI(); + + // find module according to namespace + final Module module = pathContext.getSchemaContext().findModuleByNamespace( + URI.create(namespace)).iterator().next(); + + // initialize codec + set default prefix derived from module name + final StringModuleInstanceIdentifierCodec codec = new StringModuleInstanceIdentifierCodec( + pathContext.getSchemaContext(), module.getName()); + + // find complete path to target and target schema node + // target can be also empty (only slash) + YangInstanceIdentifier targetII; + final SchemaNode targetNode; + if (target.equals("/")) { + targetII = pathContext.getInstanceIdentifier(); + targetNode = pathContext.getSchemaContext(); + } else { + targetII = codec.deserialize(codec.serialize(pathContext.getInstanceIdentifier()) + .concat(prepareNonCondXpath(schemaNode, target.replaceFirst("/", ""), firstValueElement, + namespace, module.getQNameModule().getFormattedRevision()))); + + targetNode = SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(), + codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath().getParent()); + + // move schema node + schemaNode = (DataSchemaNode) SchemaContextUtil.findDataSchemaNode(pathContext.getSchemaContext(), + codec.getDataContextTree().getChild(targetII).getDataSchemaNode().getPath()); + } + if (targetNode == null) { LOG.debug("Target node {} not found in path {} ", target, pathContext.getSchemaNode()); throw new RestconfDocumentedException("Error parsing input", ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); } else { - final YangInstanceIdentifier targetII = pathContext.getInstanceIdentifier().node(targetNode.getQName()); - final NodeList valueNodes = element.getElementsByTagName("value").item(0).getChildNodes(); - Element value = null; - for (int j = 0; j < valueNodes.getLength(); j++) { - if (valueNodes.item(j) instanceof Element) { - value = (Element) valueNodes.item(j); - break; + if (PATCHEditOperation.isPatchOperationWithValue(operation)) { + NormalizedNode parsed = null; + if (schemaNode instanceof ContainerSchemaNode) { + parsed = parserFactory.getContainerNodeParser().parse(values, (ContainerSchemaNode) schemaNode); + } else if (schemaNode instanceof ListSchemaNode) { + parsed = parserFactory.getMapNodeParser().parse(values, (ListSchemaNode) schemaNode); } - } - NormalizedNode parsed = null; - if (schemaNode instanceof ContainerSchemaNode) { - parsed = parserFactory.getContainerNodeParser().parse(Collections.singletonList(value), - (ContainerSchemaNode) targetNode); - } else if (schemaNode instanceof ListSchemaNode) { - NormalizedNode parsedValue = parserFactory.getMapEntryNodeParser().parse(Collections - .singletonList(value), (ListSchemaNode) targetNode); - parsed = ImmutableNodes.mapNodeBuilder().withNodeIdentifier(new NodeIdentifier - (targetNode.getQName())).withChild((MapEntryNode) parsedValue).build(); - } - resultCollection.add(new PATCHEntity(editId, operation, targetII, parsed)); + // for lists allow to manipulate with list items through their parent + if (targetII.getLastPathArgument() instanceof NodeIdentifierWithPredicates) { + targetII = targetII.getParent(); + } + + resultCollection.add(new PATCHEntity(editId, operation, targetII, parsed)); + } else { + resultCollection.add(new PATCHEntity(editId, operation, targetII)); + } } } return new PATCHContext(pathContext, ImmutableList.copyOf(resultCollection), patchId); } + + /** + * Read value nodes + * @param element Element of current edit operation + * @param operation Name of current operation + * @return List of value elements + */ + private static List readValueNodes(@Nonnull final Element element, @Nonnull final String operation) { + final Node valueNode = element.getElementsByTagName("value").item(0); + + if (PATCHEditOperation.isPatchOperationWithValue(operation) && (valueNode == null)) { + throw new RestconfDocumentedException("Error parsing input", + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + + if (!PATCHEditOperation.isPatchOperationWithValue(operation) && (valueNode != null)) { + throw new RestconfDocumentedException("Error parsing input", + ErrorType.PROTOCOL, ErrorTag.MALFORMED_MESSAGE); + } + + if (valueNode == null) { + return null; + } + + final List result = new ArrayList<>(); + final NodeList childNodes = valueNode.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + if (childNodes.item(i) instanceof Element) { + result.add((Element) childNodes.item(i)); + } + } + + return result; + } + + /** + * Prepare non-conditional XPath suitable for deserialization + * with {@link StringModuleInstanceIdentifierCodec} + * @param schemaNode Top schema node + * @param target Edit operation target + * @param value Element with value + * @param namespace Module namespace + * @param revision Module revision + * @return Non-conditional XPath + */ + private static String prepareNonCondXpath(@Nonnull final DataSchemaNode schemaNode, @Nonnull final String target, + @Nonnull final Element value, @Nonnull final String namespace, @Nonnull final String revision) { + final Iterator args = Splitter.on("/").split(target.substring(target.indexOf(':') + 1)).iterator(); + + final StringBuilder nonCondXpath = new StringBuilder(); + SchemaNode childNode = schemaNode; + + while (args.hasNext()) { + final String s = args.next(); + nonCondXpath.append("/"); + nonCondXpath.append(s); + childNode = ((DataNodeContainer) childNode).getDataChildByName(QName.create(namespace, revision, s)); + + if ((childNode instanceof ListSchemaNode) && args.hasNext()) { + appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), args); + } + } + + if ((childNode instanceof ListSchemaNode) && (value != null)) { + final Iterator keyValues = readKeyValues(value, + ((ListSchemaNode) childNode).getKeyDefinition().iterator()); + appendKeys(nonCondXpath, ((ListSchemaNode) childNode).getKeyDefinition().iterator(), keyValues); + } + + return nonCondXpath.toString(); + } + + /** + * Read value for every list key + * @param value Value element + * @param keys Iterator of list keys names + * @return Iterator of list keys values + */ + private static Iterator readKeyValues(@Nonnull final Element value, @Nonnull final Iterator keys) { + final List result = new ArrayList<>(); + + while (keys.hasNext()) { + result.add(value.getElementsByTagName(keys.next().getLocalName()).item(0).getFirstChild().getNodeValue()); + } + + return result.iterator(); + } + + /** + * Append key name - key value pairs for every list key to {@code nonCondXpath} + * @param nonCondXpath Builder for creating non-conditional XPath + * @param keyNames Iterator of list keys names + * @param keyValues Iterator of list keys values + */ + private static void appendKeys(@Nonnull final StringBuilder nonCondXpath, @Nonnull final Iterator keyNames, + @Nonnull final Iterator keyValues) { + while (keyNames.hasNext()) { + nonCondXpath.append("["); + nonCondXpath.append(keyNames.next().getLocalName()); + nonCondXpath.append("="); + nonCondXpath.append("'"); + nonCondXpath.append(keyValues.next()); + nonCondXpath.append("'"); + nonCondXpath.append("]"); + } + } }