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;
18 import javax.xml.stream.XMLEventReader;
19 import javax.xml.stream.XMLInputFactory;
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;
25 import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
26 import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper;
27 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO;
28 import org.opendaylight.controller.sal.restconf.impl.NodeWrapper;
29 import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
30 import org.opendaylight.yangtools.yang.data.api.Node;
32 public class XmlReader {
34 private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
35 private XMLEventReader eventReader;
37 public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException,
38 UnsupportedFormatException,
40 //Get an XML stream which can be marked, and reset, so we can check and see if there is
41 //any content being provided.
42 entityStream = getMarkableStream(entityStream);
44 if( isInputStreamEmpty( entityStream ) ) {
48 eventReader = xmlInputFactory.createXMLEventReader(entityStream);
50 if (eventReader.hasNext()) {
51 XMLEvent element = eventReader.peek();
52 if (element.isStartDocument()) {
53 eventReader.nextEvent();
57 if (eventReader.hasNext() && !isCompositeNodeEvent(eventReader.peek())) {
58 throw new UnsupportedFormatException("Root element of XML has to be composite element.");
61 final Stack<NodeWrapper<?>> processingQueue = new Stack<>();
62 CompositeNodeWrapper root = null;
63 NodeWrapper<?> element = null;
64 while (eventReader.hasNext()) {
65 final XMLEvent event = eventReader.nextEvent();
67 if (event.isStartElement()) {
68 final StartElement startElement = event.asStartElement();
69 CompositeNodeWrapper compParentNode = null;
70 if (!processingQueue.isEmpty() && processingQueue.peek() instanceof CompositeNodeWrapper) {
71 compParentNode = (CompositeNodeWrapper) processingQueue.peek();
73 NodeWrapper<?> newNode = null;
74 if (isCompositeNodeEvent(event)) {
76 root = resolveCompositeNodeFromStartElement(startElement);
79 newNode = resolveCompositeNodeFromStartElement(startElement);
81 } else if (isSimpleNodeEvent(event)) {
83 throw new UnsupportedFormatException("Root element of XML has to be composite element.");
85 newNode = resolveSimpleNodeFromStartElement(startElement);
88 if (newNode != null) {
89 processingQueue.push(newNode);
90 if (compParentNode != null) {
91 compParentNode.addValue(newNode);
94 } else if (event.isEndElement()) {
95 element = processingQueue.pop();
99 if (!root.getLocalName().equals(element.getLocalName())) {
100 throw new UnsupportedFormatException("XML should contain only one root element");
107 * If the input stream is not markable, then it wraps the input stream with a buffered stream,
108 * which is mark able. That way we can check if the stream is empty safely.
109 * @param entityStream
112 private InputStream getMarkableStream(InputStream entityStream) {
113 if( !entityStream.markSupported() )
115 entityStream = new BufferedInputStream( entityStream );
120 private boolean isInputStreamEmpty(InputStream entityStream)
122 boolean isEmpty = false;
123 entityStream.mark( 1 );
124 if( entityStream.read() == -1 ){
127 entityStream.reset();
131 private boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException {
132 checkArgument(event != null, "XML Event cannot be NULL!");
133 if (event.isStartElement()) {
134 if (eventReader.hasNext()) {
135 final XMLEvent innerEvent;
136 innerEvent = eventReader.peek();
137 if (innerEvent.isCharacters()) {
138 final Characters chars = innerEvent.asCharacters();
139 if (!chars.isWhiteSpace()) {
142 } else if (innerEvent.isEndElement()) {
150 private boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException {
151 checkArgument(event != null, "XML Event cannot be NULL!");
152 if (event.isStartElement()) {
153 if (eventReader.hasNext()) {
155 innerEvent = eventReader.peek();
156 if (innerEvent.isCharacters()) {
157 Characters chars = innerEvent.asCharacters();
158 if (chars.isWhiteSpace()) {
159 eventReader.nextEvent();
160 innerEvent = eventReader.peek();
163 if (innerEvent.isStartElement()) {
171 private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final StartElement startElement) {
172 checkArgument(startElement != null, "Start Element cannot be NULL!");
173 return new CompositeNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
176 private NodeWrapper<? extends Node<?>> resolveSimpleNodeFromStartElement(final StartElement startElement)
177 throws XMLStreamException {
178 checkArgument(startElement != null, "Start Element cannot be NULL!");
179 String data = getValueOf(startElement);
181 return new EmptyNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
183 return new SimpleNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement),
184 resolveValueOfElement(data, startElement));
187 private String getValueOf(StartElement startElement) throws XMLStreamException {
189 if (eventReader.hasNext()) {
190 final XMLEvent innerEvent = eventReader.peek();
191 if (innerEvent.isCharacters()) {
192 final Characters chars = innerEvent.asCharacters();
193 if (!chars.isWhiteSpace()) {
194 data = innerEvent.asCharacters().getData();
195 data = data + getAdditionalData(eventReader.nextEvent());
197 } else if (innerEvent.isEndElement()) {
198 if (startElement.getLocation().getCharacterOffset() == innerEvent.getLocation().getCharacterOffset()) {
205 return data == null ? null : data.trim();
208 private String getAdditionalData(XMLEvent event) throws XMLStreamException {
210 if (eventReader.hasNext()) {
211 final XMLEvent innerEvent = eventReader.peek();
212 if (innerEvent.isCharacters() && !innerEvent.isEndElement()) {
213 final Characters chars = innerEvent.asCharacters();
214 if (!chars.isWhiteSpace()) {
215 data = innerEvent.asCharacters().getData();
216 data = data + getAdditionalData(eventReader.nextEvent());
223 private String getLocalNameFor(StartElement startElement) {
224 return startElement.getName().getLocalPart();
227 private URI getNamespaceFor(StartElement startElement) {
228 String namespaceURI = startElement.getName().getNamespaceURI();
229 return namespaceURI.isEmpty() ? null : URI.create(namespaceURI);
232 private Object resolveValueOfElement(String value, StartElement startElement) {
233 // it could be instance-identifier Built-In Type
234 if (value.startsWith("/")) {
235 IdentityValuesDTO iiValue = RestUtil.asInstanceIdentifier(value, new RestUtil.PrefixMapingFromXml(startElement));
236 if (iiValue != null) {
240 // it could be identityref Built-In Type
241 String[] namespaceAndValue = value.split(":");
242 if (namespaceAndValue.length == 2) {
243 String namespace = startElement.getNamespaceContext().getNamespaceURI(namespaceAndValue[0]);
244 if (namespace != null && !namespace.isEmpty()) {
245 return new IdentityValuesDTO(namespace, namespaceAndValue[1], namespaceAndValue[0],value);
248 // it is not "prefix:value" but just "value"