2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.controller.sal.rest.impl;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import java.io.BufferedInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
16 import java.util.Stack;
17 import javax.xml.stream.XMLEventReader;
18 import javax.xml.stream.XMLInputFactory;
19 import javax.xml.stream.XMLStreamConstants;
20 import javax.xml.stream.XMLStreamException;
21 import javax.xml.stream.events.Characters;
22 import javax.xml.stream.events.StartElement;
23 import javax.xml.stream.events.XMLEvent;
24 import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
25 import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper;
26 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO;
27 import org.opendaylight.controller.sal.restconf.impl.NodeWrapper;
28 import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
29 import org.opendaylight.yangtools.yang.data.api.Node;
31 public class XmlToCompositeNodeReader {
33 private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
34 private XMLEventReader eventReader;
36 public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, UnsupportedFormatException,
38 // Get an XML stream which can be marked, and reset, so we can check and
40 // any content being provided.
41 entityStream = getMarkableStream(entityStream);
43 if (isInputStreamEmpty(entityStream)) {
47 eventReader = xmlInputFactory.createXMLEventReader(entityStream);
49 if (eventReader.hasNext()) {
50 XMLEvent element = eventReader.peek();
51 if (element.isStartDocument()) {
52 eventReader.nextEvent();
56 if (eventReader.hasNext() && !isCompositeNodeEvent(eventReader.peek())) {
57 throw new UnsupportedFormatException("Root element of XML has to be composite element.");
60 final Stack<NodeWrapper<?>> processingQueue = new Stack<>();
61 CompositeNodeWrapper root = null;
62 NodeWrapper<?> element = null;
63 while (eventReader.hasNext()) {
64 final XMLEvent event = eventReader.nextEvent();
66 if (event.isStartElement()) {
67 final StartElement startElement = event.asStartElement();
68 CompositeNodeWrapper compParentNode = null;
69 if (!processingQueue.isEmpty() && processingQueue.peek() instanceof CompositeNodeWrapper) {
70 compParentNode = (CompositeNodeWrapper) processingQueue.peek();
72 NodeWrapper<?> newNode = null;
73 if (isCompositeNodeEvent(event)) {
75 root = resolveCompositeNodeFromStartElement(startElement);
78 newNode = resolveCompositeNodeFromStartElement(startElement);
80 } else if (isSimpleNodeEvent(event)) {
82 throw new UnsupportedFormatException("Root element of XML has to be composite element.");
84 newNode = resolveSimpleNodeFromStartElement(startElement);
87 if (newNode != null) {
88 processingQueue.push(newNode);
89 if (compParentNode != null) {
90 compParentNode.addValue(newNode);
93 } else if (event.isEndElement()) {
94 element = processingQueue.pop();
98 if (!root.getLocalName().equals(element.getLocalName())) {
99 throw new UnsupportedFormatException("XML should contain only one root element");
106 * If the input stream is not markable, then it wraps the input stream with
107 * a buffered stream, which is mark able. That way we can check if the
108 * stream is empty safely.
110 * @param entityStream
113 private InputStream getMarkableStream(InputStream entityStream) {
114 if (!entityStream.markSupported()) {
115 entityStream = new BufferedInputStream(entityStream);
120 private boolean isInputStreamEmpty(InputStream entityStream) throws IOException {
121 boolean isEmpty = false;
122 entityStream.mark(1);
123 if (entityStream.read() == -1) {
126 entityStream.reset();
130 private boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException {
131 checkArgument(event != null, "XML Event cannot be NULL!");
132 if (event.isStartElement()) {
133 XMLEvent innerEvent = skipCommentsAndWhitespace();
134 if (innerEvent != null && (innerEvent.isCharacters() || innerEvent.isEndElement())) {
141 private boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException {
142 checkArgument(event != null, "XML Event cannot be NULL!");
143 if (event.isStartElement()) {
144 XMLEvent innerEvent = skipCommentsAndWhitespace();
145 if (innerEvent != null) {
146 if (innerEvent.isStartElement()) {
154 private XMLEvent skipCommentsAndWhitespace() throws XMLStreamException {
155 while (eventReader.hasNext()) {
156 XMLEvent event = eventReader.peek();
157 if (event.getEventType() == XMLStreamConstants.COMMENT) {
158 eventReader.nextEvent();
162 if (event.isCharacters()) {
163 Characters chars = event.asCharacters();
164 if (chars.isWhiteSpace()) {
165 eventReader.nextEvent();
174 private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final StartElement startElement) {
175 checkArgument(startElement != null, "Start Element cannot be NULL!");
176 return new CompositeNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
179 private NodeWrapper<? extends Node<?>> resolveSimpleNodeFromStartElement(final StartElement startElement)
180 throws XMLStreamException {
181 checkArgument(startElement != null, "Start Element cannot be NULL!");
182 String data = getValueOf(startElement);
184 return new EmptyNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
186 return new SimpleNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement),
187 resolveValueOfElement(data, startElement));
190 private String getValueOf(StartElement startElement) throws XMLStreamException {
192 if (eventReader.hasNext()) {
193 final XMLEvent innerEvent = eventReader.peek();
194 if (innerEvent.isCharacters()) {
195 final Characters chars = innerEvent.asCharacters();
196 if (!chars.isWhiteSpace()) {
197 data = innerEvent.asCharacters().getData();
198 data = data + getAdditionalData(eventReader.nextEvent());
200 } else if (innerEvent.isEndElement()) {
201 if (startElement.getLocation().getCharacterOffset() == innerEvent.getLocation().getCharacterOffset()) {
208 return data == null ? null : data.trim();
211 private String getAdditionalData(XMLEvent event) throws XMLStreamException {
213 if (eventReader.hasNext()) {
214 final XMLEvent innerEvent = eventReader.peek();
215 if (innerEvent.isCharacters() && !innerEvent.isEndElement()) {
216 final Characters chars = innerEvent.asCharacters();
217 if (!chars.isWhiteSpace()) {
218 data = innerEvent.asCharacters().getData();
219 data = data + getAdditionalData(eventReader.nextEvent());
226 private String getLocalNameFor(StartElement startElement) {
227 return startElement.getName().getLocalPart();
230 private URI getNamespaceFor(StartElement startElement) {
231 String namespaceURI = startElement.getName().getNamespaceURI();
232 return namespaceURI.isEmpty() ? null : URI.create(namespaceURI);
235 private Object resolveValueOfElement(String value, StartElement startElement) {
236 // it could be instance-identifier Built-In Type
237 if (value.startsWith("/")) {
238 IdentityValuesDTO iiValue = RestUtil.asInstanceIdentifier(value, new RestUtil.PrefixMapingFromXml(
240 if (iiValue != null) {
244 // it could be identityref Built-In Type
245 String[] namespaceAndValue = value.split(":");
246 if (namespaceAndValue.length == 2) {
247 String namespace = startElement.getNamespaceContext().getNamespaceURI(namespaceAndValue[0]);
248 if (namespace != null && !namespace.isEmpty()) {
249 return new IdentityValuesDTO(namespace, namespaceAndValue[1], namespaceAndValue[0], value);
252 // it is not "prefix:value" but just "value"