Merge "Add missing copyright text"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / rest / impl / XmlNormalizedNodeBodyReader.java
index 062a4488f369075a70c4322f4d75b5a1ce24841b..2a9c5bf190cbed62c4756940942436ac8f88682e 100644 (file)
@@ -7,11 +7,12 @@
  */
 package org.opendaylight.controller.sal.rest.impl;
 
-import com.google.common.base.Optional;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import javax.ws.rs.Consumes;
@@ -33,9 +34,16 @@ import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
 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.AugmentationSchema;
+import org.opendaylight.yangtools.yang.model.api.AugmentationTarget;
+import org.opendaylight.yangtools.yang.model.api.ChoiceCaseNode;
+import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
 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.RpcDefinition;
+import org.opendaylight.yangtools.yang.model.api.SchemaNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.w3c.dom.Document;
@@ -47,11 +55,19 @@ import org.w3c.dom.Element;
 public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsProvider implements MessageBodyReader<NormalizedNodeContext> {
 
     private final static Logger LOG = LoggerFactory.getLogger(XmlNormalizedNodeBodyReader.class);
-    private final static DomToNormalizedNodeParserFactory DOM_PARSER_FACTORY = DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER);
     private static final DocumentBuilderFactory BUILDERFACTORY;
 
     static {
-        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+        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);
@@ -71,34 +87,113 @@ public class XmlNormalizedNodeBodyReader extends AbstractIdentifierAwareJaxRsPro
             final MultivaluedMap<String, String> httpHeaders, final InputStream entityStream) throws IOException,
             WebApplicationException {
         try {
-            Optional<InstanceIdentifierContext> path = getIdentifierWithSchema();
+            final InstanceIdentifierContext<?> path = getInstanceIdentifierContext();
+
+            if (entityStream.available() < 1) {
+                // represent empty nopayload input
+                return new NormalizedNodeContext(path, null);
+            }
 
             final DocumentBuilder dBuilder;
             try {
                 dBuilder = BUILDERFACTORY.newDocumentBuilder();
-            } catch (ParserConfigurationException e) {
+            } catch (final ParserConfigurationException e) {
                 throw new RuntimeException("Failed to parse XML document", e);
             }
-            Document doc = dBuilder.parse(entityStream);
+            final Document doc = dBuilder.parse(entityStream);
 
-            NormalizedNode<?, ?> result = parse(path.get(),doc);
-            return new NormalizedNodeContext(path.get(),result);
-        } catch (Exception e) {
-            LOG.debug("Error parsing json input", e);
+            final NormalizedNode<?, ?> result = parse(path,doc);
+            return new NormalizedNodeContext(path,result);
+        } catch (final RestconfDocumentedException e){
+            throw e;
+        } catch (final Exception e) {
+            LOG.debug("Error parsing xml input", e);
 
             throw new RestconfDocumentedException("Error parsing input: " + e.getMessage(), ErrorType.PROTOCOL,
                     ErrorTag.MALFORMED_MESSAGE);
         }
     }
 
-    private static NormalizedNode<?,?> parse(InstanceIdentifierContext pathContext,Document doc) {
-        List<Element> elements = Collections.singletonList(doc.getDocumentElement());
-        DataSchemaNode schemaNode = pathContext.getSchemaNode();
+    private static NormalizedNode<?,?> parse(final InstanceIdentifierContext<?> pathContext,final Document doc) {
+
+        final List<Element> elements = Collections.singletonList(doc.getDocumentElement());
+        final SchemaNode schemaNodeContext = pathContext.getSchemaNode();
+        DataSchemaNode schemaNode;
+        if (schemaNodeContext instanceof RpcDefinition) {
+            schemaNode = ((RpcDefinition) schemaNodeContext).getInput();
+        } else if (schemaNodeContext instanceof DataSchemaNode) {
+            schemaNode = (DataSchemaNode) schemaNodeContext;
+        } else {
+            throw new IllegalStateException("Unknow SchemaNode");
+        }
+
+        final String docRootElm = doc.getDocumentElement().getLocalName();
+        final String schemaNodeName = pathContext.getSchemaNode().getQName().getLocalName();
+
+        // FIXME the factory instance should be cached if the schema context is the same
+        final DomToNormalizedNodeParserFactory parserFactory =
+                DomToNormalizedNodeParserFactory.getInstance(XmlUtils.DEFAULT_XML_CODEC_PROVIDER, pathContext.getSchemaContext());
+
+        if (!schemaNodeName.equalsIgnoreCase(docRootElm)) {
+            final DataSchemaNode foundSchemaNode = findSchemaNodeOrParentChoiceByName(schemaNode, docRootElm);
+            if (foundSchemaNode != null) {
+                if (schemaNode instanceof AugmentationTarget) {
+                    final AugmentationSchema augmentSchemaNode = findCorrespondingAugment(schemaNode, foundSchemaNode);
+                    if (augmentSchemaNode != null) {
+                        return parserFactory.getAugmentationNodeParser().parse(elements, augmentSchemaNode);
+                    }
+                }
+                schemaNode = foundSchemaNode;
+            }
+        }
+
+        NormalizedNode<?, ?> parsed = null;
+
         if(schemaNode instanceof ContainerSchemaNode) {
-            return DOM_PARSER_FACTORY.getContainerNodeParser().parse(Collections.singletonList(doc.getDocumentElement()), (ContainerSchemaNode) schemaNode);
+            return parserFactory.getContainerNodeParser().parse(Collections.singletonList(doc.getDocumentElement()), (ContainerSchemaNode) schemaNode);
         } else if(schemaNode instanceof ListSchemaNode) {
-            ListSchemaNode casted = (ListSchemaNode) schemaNode;
-            return DOM_PARSER_FACTORY.getMapEntryNodeParser().parse(elements, casted);
+            final ListSchemaNode casted = (ListSchemaNode) schemaNode;
+            return parserFactory.getMapEntryNodeParser().parse(elements, casted);
+        } else if (schemaNode instanceof ChoiceSchemaNode) {
+            final ChoiceSchemaNode casted = (ChoiceSchemaNode) schemaNode;
+            return parserFactory.getChoiceNodeParser().parse(elements, casted);
+        }
+        // FIXME : add another DataSchemaNode extensions e.g. LeafSchemaNode
+
+        return parsed;
+    }
+
+    private static DataSchemaNode findSchemaNodeOrParentChoiceByName(DataSchemaNode schemaNode, String elementName) {
+        final ArrayList<ChoiceSchemaNode> choiceSchemaNodes = new ArrayList<>();
+        final Collection<DataSchemaNode> children = ((DataNodeContainer) schemaNode).getChildNodes();
+        for (final DataSchemaNode child : children) {
+            if (child instanceof ChoiceSchemaNode) {
+                choiceSchemaNodes.add((ChoiceSchemaNode) child);
+            } else if (child.getQName().getLocalName().equalsIgnoreCase(elementName)) {
+                return child;
+            }
+        }
+
+        for (final ChoiceSchemaNode choiceNode : choiceSchemaNodes) {
+            for (final ChoiceCaseNode caseNode : choiceNode.getCases()) {
+                final DataSchemaNode resultFromRecursion = findSchemaNodeOrParentChoiceByName(caseNode, elementName);
+                if (resultFromRecursion != null) {
+                    // this returns top choice node in which child element is found
+                    return choiceNode;
+                }
+            }
+        }
+        return null;
+    }
+
+    private static AugmentationSchema findCorrespondingAugment(final DataSchemaNode parent, final DataSchemaNode child) {
+        if (parent instanceof AugmentationTarget && !((parent instanceof ChoiceCaseNode) || (parent instanceof ChoiceSchemaNode))) {
+            for (AugmentationSchema augmentation : ((AugmentationTarget) parent).getAvailableAugmentations()) {
+                DataSchemaNode childInAugmentation = augmentation.getDataChildByName(child.getQName());
+                if (childInAugmentation != null) {
+                    return augmentation;
+                }
+            }
         }
         return null;
     }