Added XmlTreeBuilder into yang-data-impl 19/1219/1
authorlsedlak <lsedlak@cisco.com>
Tue, 17 Sep 2013 11:32:12 +0000 (13:32 +0200)
committerlsedlak <lsedlak@cisco.com>
Tue, 17 Sep 2013 12:37:04 +0000 (14:37 +0200)
Added implementation of XML Tree Builder to facilitate and support of loading XML files and providing YANG DOM composed of CompositeNodes and SimpleNodes;
Added TestCase and test resources for XML Tree Builder;

Signed-off-by: Lukas Sedlak <lsedlak@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilder.java [new file with mode: 0644]
yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilderTest.java [new file with mode: 0644]
yang/yang-data-impl/src/test/resources/rpc-getDeviceEquipment.xml [new file with mode: 0644]

diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilder.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilder.java
new file mode 100644 (file)
index 0000000..3904555
--- /dev/null
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 2013 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.impl;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.io.InputStream;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Stack;
+
+import javax.xml.stream.XMLEventReader;
+import javax.xml.stream.XMLInputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.events.Characters;
+import javax.xml.stream.events.StartElement;
+import javax.xml.stream.events.XMLEvent;
+
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.ModifyAction;
+import org.opendaylight.yangtools.yang.data.api.MutableCompositeNode;
+import org.opendaylight.yangtools.yang.data.api.Node;
+import org.opendaylight.yangtools.yang.data.api.SimpleNode;
+
+/**
+ * The XML Tree Builder is builder utility class designed to facilitate of
+ * loading, reading and parsing XML documents into YANG DOM represented by
+ * yang-data-api. <br>
+ * The method {@link #buildDataTree(InputStream)} is designed to take and parse
+ * XML as {@link InputStream}. The output of the operation SHOULD be root
+ * <code>CompositeNode</code> or <code>SimpleElement</code> depends by which
+ * element XML begins. The XML header is omitted by XML parser.
+ * 
+ * @author Lukas Sedlak
+ * 
+ * @see CompositeNode
+ * @see SimpleNode
+ * @see Node
+ */
+public final class XmlTreeBuilder {
+
+       private final static XMLInputFactory xmlInputFactory = XMLInputFactory
+                       .newInstance();
+       private static XMLEventReader eventReader;
+
+       private XmlTreeBuilder() {
+       }
+
+       /**
+        * The method is designed to take and parse XML as {@link InputStream}. The
+        * output of the operation SHOULD be root <code>CompositeNode</code> or
+        * <code>SimpleElement</code> depends on element that XML document begins.
+        * The XML header is omitted by XML parser.
+        * 
+        * @param inputStream
+        *            XML Input Stream
+        * @return root <code>Node</code> element conformant to XML start element in
+        *         most cases it will be CompositeNode which contains child Nodes
+        * @throws XMLStreamException
+        */
+       public static Node<?> buildDataTree(final InputStream inputStream)
+                       throws XMLStreamException {
+               eventReader = xmlInputFactory.createXMLEventReader(inputStream);
+
+               final Stack<Node<?>> processingQueue = new Stack<>();
+               Node<?> parentNode = null;
+               Node<?> root = null;
+               while (eventReader.hasNext()) {
+                       final XMLEvent event = eventReader.nextEvent();
+
+                       if (event.isStartElement()) {
+                               final StartElement startElement = event.asStartElement();
+                               if (!processingQueue.isEmpty()) {
+                                       parentNode = processingQueue.peek();
+                               }
+                               CompositeNode compParentNode = null;
+                               if (parentNode instanceof CompositeNode) {
+                                       compParentNode = (CompositeNode) parentNode;
+                               }
+                               Node<?> newNode = null;
+                               if (isCompositeNodeEvent(event)) {
+                                       newNode = resolveCompositeNodeFromStartElement(
+                                                       startElement, compParentNode);
+                               } else if (isSimpleNodeEvent(event)) {
+                                       newNode = resolveSimpleNodeFromStartElement(startElement,
+                                                       compParentNode);
+                               }
+
+                               if (newNode != null) {
+                                       processingQueue.push(newNode);
+                                       if (compParentNode != null) {
+                                               compParentNode.getChildren().add(newNode);
+                                       }
+                               }
+                       } else if (event.isEndElement()) {
+                               root = processingQueue.pop();
+                       }
+               }
+               return root;
+       }
+
+       /**
+        * Checks if the XMLEvent is compliant to SimpleNode tag that contains only
+        * characters value. If the SimpleNode is composed only by empty XML tag
+        * (i.e. {@code <emptyTag />} or {@code<emptyTag></emptyTag>}) the result
+        * will be also <code>true</code>.
+        * 
+        * @param event
+        *            actual XMLEvent that is processed
+        * @return <code>true</code> only and only if the XMLEvent Start Element is
+        *         Simple element tag and contains character values or is empty XML
+        *         tag.
+        * 
+        * @see SimpleNode
+        */
+       private static boolean isSimpleNodeEvent(final XMLEvent event) {
+               checkArgument(event != null, "XML Event cannot be NULL!");
+               if (event.isStartElement()) {
+                       if (eventReader.hasNext()) {
+                               final XMLEvent innerEvent;
+                               try {
+                                       innerEvent = eventReader.peek();
+                                       if (innerEvent.isCharacters()) {
+                                               final Characters chars = innerEvent.asCharacters();
+                                               if (!chars.isWhiteSpace()) {
+                                                       return true;
+                                               }
+                                       } else if (innerEvent.isEndElement()) {
+                                               return true;
+                                       }
+                               } catch (XMLStreamException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Checks if XMLEvent is equivalent to CompositeNode Event. The
+        * CompositeNode Event is XML element that conforms to the XML element that
+        * contains 1..N XML child elements. (i.e. {@code <compositeNode>
+        *      <simpleNode>data</simpleNode>
+        * </compositeNode>})
+        * 
+        * @param event
+        *            actual XMLEvent that is processed
+        * @return <code>true</code> only if XML Element contains 1..N child
+        *         elements, otherwise returns <code>false</code>
+        * 
+        * @see CompositeNode
+        */
+       private static boolean isCompositeNodeEvent(final XMLEvent event) {
+               checkArgument(event != null, "XML Event cannot be NULL!");
+               if (event.isStartElement()) {
+                       if (eventReader.hasNext()) {
+                               XMLEvent innerEvent;
+                               try {
+                                       innerEvent = eventReader.peek();
+                                       if (innerEvent.isCharacters()) {
+                                               Characters chars = innerEvent.asCharacters();
+                                               if (chars.isWhiteSpace()) {
+                                                       eventReader.nextEvent();
+                                                       innerEvent = eventReader.peek();
+                                               }
+                                       }
+                                       if (innerEvent.isStartElement()) {
+                                               return true;
+                                       }
+                               } catch (XMLStreamException e) {
+                                       e.printStackTrace();
+                               }
+                       }
+               }
+               return false;
+       }
+
+       /**
+        * Creates and returns <code>SimpleNode</code> instance from actually
+        * processed XML Start Element.
+        * 
+        * @param startElement
+        *            actual XML Start Element that is processed
+        * @param parent
+        *            Parent CompositeNode
+        * @return <code>new SimpleNode</code> instance from actually processed XML
+        *         Start Element
+        * @throws XMLStreamException
+        * 
+        * @see SimpleNode
+        */
+       private static SimpleNode<String> resolveSimpleNodeFromStartElement(
+                       final StartElement startElement, CompositeNode parent)
+                       throws XMLStreamException {
+               checkArgument(startElement != null, "Start Element cannot be NULL!");
+               String data = null;
+
+               if (eventReader.hasNext()) {
+                       final XMLEvent innerEvent = eventReader.peek();
+                       if (innerEvent.isCharacters()) {
+                               final Characters chars = innerEvent.asCharacters();
+                               if (!chars.isWhiteSpace()) {
+                                       data = innerEvent.asCharacters().getData();
+                               }
+                       } else if (innerEvent.isEndElement()) {
+                               data = "";
+                       }
+               }
+               return NodeFactory.createImmutableSimpleNode(
+                               resolveElementQName(startElement), parent, data);
+       }
+
+       /**
+        * Creates and returns <code>MutableCompositeNode</code> instance from
+        * actually processed XML Start Element.
+        * 
+        * @param startElement
+        *            actual XML Start Element that is processed
+        * @param parent
+        *            Parent CompositeNode
+        * @return <code>new MutableCompositeNode</code> instance from actually
+        *         processed XML Start Element
+        * 
+        * @see CompositeNode
+        * @see MutableCompositeNode
+        */
+       private static CompositeNode resolveCompositeNodeFromStartElement(
+                       final StartElement startElement, CompositeNode parent) {
+               checkArgument(startElement != null, "Start Element cannot be NULL!");
+
+               return NodeFactory.createMutableCompositeNode(
+                               resolveElementQName(startElement), parent,
+                               new ArrayList<Node<?>>(), ModifyAction.CREATE, null);
+       }
+
+       /**
+        * Extract and retrieve XML Element QName to OpenDaylight QName.
+        * 
+        * @param element
+        *            Start Element
+        * @return QName instance composed of <code>elements</code> Namespace and
+        *         Local Part.
+        * 
+        * @see QName
+        */
+       private static QName resolveElementQName(final StartElement element) {
+               checkArgument(element != null, "Start Element cannot be NULL!");
+
+               final String nsURI = element.getName().getNamespaceURI();
+               final String localName = element.getName().getLocalPart();
+               return new QName(URI.create(nsURI), localName);
+       }
+}
diff --git a/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilderTest.java b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilderTest.java
new file mode 100644 (file)
index 0000000..6f82534
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2013 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.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+
+import javax.xml.stream.XMLStreamException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.yangtools.yang.data.api.CompositeNode;
+import org.opendaylight.yangtools.yang.data.api.Node;
+import org.opendaylight.yangtools.yang.data.api.SimpleNode;
+
+/**
+ * The class <code>XmlTreeBuilderTest</code> contains tests for the class
+ * {@link <code>XmlTreeBuilder</code>}
+ *
+ * @author Lukas Sedlak
+ */
+public class XmlTreeBuilderTest {
+
+       private InputStream inputStream;
+       
+       /**
+        * Perform pre-test initialization
+        * 
+        * @throws Exception
+        */
+       @Before
+       public void setUp() throws Exception {
+               final String inputFile = getClass().getResource("/rpc-getDeviceEquipment.xml").getPath();
+               inputStream = new FileInputStream(inputFile);
+       }
+
+       /**
+        * Run the Node<?> buildDataTree(InputStream) method test
+        */
+       @SuppressWarnings("unchecked")
+       @Test
+       public void testBuildDataTree()
+       {
+               Node<?> rootNode = null;
+               try {
+                       rootNode = XmlTreeBuilder.buildDataTree(inputStream);
+               } catch (XMLStreamException e) {
+                       e.printStackTrace();
+               }
+               assertNotNull(rootNode);
+               assertTrue(rootNode instanceof CompositeNode);
+               
+               CompositeNode compRootNode = (CompositeNode)rootNode;
+               assertNotNull(compRootNode.getChildren());
+               
+               SimpleNode<String> methodName = null;
+               SimpleNode<String> emptyTag = null;
+               CompositeNode params = null;
+               for (final Node<?> childNode : compRootNode.getChildren()) {
+                       if (childNode instanceof SimpleNode) {
+                               if ("emptyTag".equals(childNode.getNodeType().getLocalName())) {
+                                       emptyTag = (SimpleNode<String>) childNode;
+                               } else if ("methodName".equals(childNode.getNodeType().getLocalName())) {
+                                       methodName = (SimpleNode<String>) childNode;
+                               }
+                               
+                       } else if (childNode instanceof CompositeNode) {
+                               params = (CompositeNode) childNode;
+                       }
+               }
+               
+               assertNotNull(methodName);
+               assertNotNull(params);
+               assertTrue(emptyTag.getValue().isEmpty());
+               assertEquals(methodName.getValue(), "getDeviceEquipment");
+               
+               String deviceId = null;
+               String deviceIP = null;
+               for (final Node<?> param : params.getChildren()) {
+                       if (param instanceof CompositeNode) {
+                               final Node<?> valueNode = ((CompositeNode) param).getChildren().get(0);
+                               
+                               assertTrue(valueNode instanceof CompositeNode);
+                               final CompositeNode value = (CompositeNode) valueNode;
+                               final Node<?> stringNode = value.getChildren().get(0);
+                               assertTrue(stringNode instanceof SimpleNode);
+                               
+                               final SimpleNode<String> string = (SimpleNode<String>) stringNode;
+                               if ("DeviceID123".equals(string.getValue())) {
+                                       deviceId = string.getValue();
+                               } else if ("172.23.218.75".equals(string.getValue())) {
+                                       deviceIP = string.getValue();
+                               }
+                       }
+               }
+               
+               assertNotNull(deviceId);
+               assertNotNull(deviceIP);
+       }
+}
\ No newline at end of file
diff --git a/yang/yang-data-impl/src/test/resources/rpc-getDeviceEquipment.xml b/yang/yang-data-impl/src/test/resources/rpc-getDeviceEquipment.xml
new file mode 100644 (file)
index 0000000..7d0b9bf
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<methodCall xmlns="urn:ciscodaylight:controller:get.device.equipment">
+       <methodName>getDeviceEquipment</methodName>
+       <emptyTag />
+       <params>
+               <param>
+                       <value>
+                               <string>DeviceID123</string>
+                       </value>
+               </param>
+               <param>
+                       <value>
+                               <string>172.23.218.75</string>
+                       </value>
+               </param>
+       </params>
+</methodCall>
\ No newline at end of file