From 4ad6d510ef82a6fecd4f1b8a755d6d5924d30f67 Mon Sep 17 00:00:00 2001 From: lsedlak Date: Tue, 17 Sep 2013 13:32:12 +0200 Subject: [PATCH] Added XmlTreeBuilder into yang-data-impl 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 --- .../yang/data/impl/XmlTreeBuilder.java | 259 ++++++++++++++++++ .../yang/data/impl/XmlTreeBuilderTest.java | 109 ++++++++ .../test/resources/rpc-getDeviceEquipment.xml | 17 ++ 3 files changed, 385 insertions(+) create mode 100644 yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilder.java create mode 100644 yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilderTest.java create mode 100644 yang/yang-data-impl/src/test/resources/rpc-getDeviceEquipment.xml 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 index 0000000000..3904555885 --- /dev/null +++ b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilder.java @@ -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.
+ * The method {@link #buildDataTree(InputStream)} is designed to take and parse + * XML as {@link InputStream}. The output of the operation SHOULD be root + * CompositeNode or SimpleElement 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 CompositeNode or + * SimpleElement depends on element that XML document begins. + * The XML header is omitted by XML parser. + * + * @param inputStream + * XML Input Stream + * @return root Node 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> 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 } or {@code}) the result + * will be also true. + * + * @param event + * actual XMLEvent that is processed + * @return true 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 + * data + * }) + * + * @param event + * actual XMLEvent that is processed + * @return true only if XML Element contains 1..N child + * elements, otherwise returns false + * + * @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 SimpleNode instance from actually + * processed XML Start Element. + * + * @param startElement + * actual XML Start Element that is processed + * @param parent + * Parent CompositeNode + * @return new SimpleNode instance from actually processed XML + * Start Element + * @throws XMLStreamException + * + * @see SimpleNode + */ + private static SimpleNode 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 MutableCompositeNode instance from + * actually processed XML Start Element. + * + * @param startElement + * actual XML Start Element that is processed + * @param parent + * Parent CompositeNode + * @return new MutableCompositeNode 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>(), ModifyAction.CREATE, null); + } + + /** + * Extract and retrieve XML Element QName to OpenDaylight QName. + * + * @param element + * Start Element + * @return QName instance composed of elements 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 index 0000000000..6f8253407a --- /dev/null +++ b/yang/yang-data-impl/src/test/java/org/opendaylight/yangtools/yang/data/impl/XmlTreeBuilderTest.java @@ -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 XmlTreeBuilderTest contains tests for the class + * {@link XmlTreeBuilder} + * + * @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 methodName = null; + SimpleNode emptyTag = null; + CompositeNode params = null; + for (final Node childNode : compRootNode.getChildren()) { + if (childNode instanceof SimpleNode) { + if ("emptyTag".equals(childNode.getNodeType().getLocalName())) { + emptyTag = (SimpleNode) childNode; + } else if ("methodName".equals(childNode.getNodeType().getLocalName())) { + methodName = (SimpleNode) 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 = (SimpleNode) 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 index 0000000000..7d0b9bf6ce --- /dev/null +++ b/yang/yang-data-impl/src/test/resources/rpc-getDeviceEquipment.xml @@ -0,0 +1,17 @@ + + + getDeviceEquipment + + + + + DeviceID123 + + + + + 172.23.218.75 + + + + \ No newline at end of file -- 2.36.6