Bug 1112: Update toaster to use async best practices
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / rest / impl / XmlToCompositeNodeReader.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.controller.sal.rest.impl;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import java.io.BufferedInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.net.URI;
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;
30
31 public class XmlToCompositeNodeReader {
32
33     private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
34     private XMLEventReader eventReader;
35
36     public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, UnsupportedFormatException,
37             IOException {
38         // Get an XML stream which can be marked, and reset, so we can check and
39         // see if there is
40         // any content being provided.
41         entityStream = getMarkableStream(entityStream);
42
43         if (isInputStreamEmpty(entityStream)) {
44             return null;
45         }
46
47         eventReader = xmlInputFactory.createXMLEventReader(entityStream);
48
49         if (eventReader.hasNext()) {
50             XMLEvent element = eventReader.peek();
51             if (element.isStartDocument()) {
52                 eventReader.nextEvent();
53             }
54         }
55
56         if (eventReader.hasNext() && !isCompositeNodeEvent(eventReader.peek())) {
57             throw new UnsupportedFormatException("Root element of XML has to be composite element.");
58         }
59
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();
65
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();
71                 }
72                 NodeWrapper<?> newNode = null;
73                 if (isCompositeNodeEvent(event)) {
74                     if (root == null) {
75                         root = resolveCompositeNodeFromStartElement(startElement);
76                         newNode = root;
77                     } else {
78                         newNode = resolveCompositeNodeFromStartElement(startElement);
79                     }
80                 } else if (isSimpleNodeEvent(event)) {
81                     if (root == null) {
82                         throw new UnsupportedFormatException("Root element of XML has to be composite element.");
83                     }
84                     newNode = resolveSimpleNodeFromStartElement(startElement);
85                 }
86
87                 if (newNode != null) {
88                     processingQueue.push(newNode);
89                     if (compParentNode != null) {
90                         compParentNode.addValue(newNode);
91                     }
92                 }
93             } else if (event.isEndElement()) {
94                 element = processingQueue.pop();
95             }
96         }
97
98         if (!root.getLocalName().equals(element.getLocalName())) {
99             throw new UnsupportedFormatException("XML should contain only one root element");
100         }
101
102         return root;
103     }
104
105     /**
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.
109      *
110      * @param entityStream
111      * @return
112      */
113     private InputStream getMarkableStream(InputStream entityStream) {
114         if (!entityStream.markSupported()) {
115             entityStream = new BufferedInputStream(entityStream);
116         }
117         return entityStream;
118     }
119
120     private boolean isInputStreamEmpty(InputStream entityStream) throws IOException {
121         boolean isEmpty = false;
122         entityStream.mark(1);
123         if (entityStream.read() == -1) {
124             isEmpty = true;
125         }
126         entityStream.reset();
127         return isEmpty;
128     }
129
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())) {
135                 return true;
136             }
137         }
138         return false;
139     }
140
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()) {
147                     return true;
148                 }
149             }
150         }
151         return false;
152     }
153
154     private XMLEvent skipCommentsAndWhitespace() throws XMLStreamException {
155         while (eventReader.hasNext()) {
156             XMLEvent event = eventReader.peek();
157             if (event.getEventType() == XMLStreamConstants.COMMENT) {
158                 eventReader.nextEvent();
159                 continue;
160             }
161
162             if (event.isCharacters()) {
163                 Characters chars = event.asCharacters();
164                 if (chars.isWhiteSpace()) {
165                     eventReader.nextEvent();
166                     continue;
167                 }
168             }
169             return event;
170         }
171         return null;
172     }
173
174     private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final StartElement startElement) {
175         checkArgument(startElement != null, "Start Element cannot be NULL!");
176         return new CompositeNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
177     }
178
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);
183         if (data == null) {
184             return new EmptyNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
185         }
186         return new SimpleNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement),
187                 resolveValueOfElement(data, startElement));
188     }
189
190     private String getValueOf(StartElement startElement) throws XMLStreamException {
191         String data = null;
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());
199                 }
200             } else if (innerEvent.isEndElement()) {
201                 if (startElement.getLocation().getCharacterOffset() == innerEvent.getLocation().getCharacterOffset()) {
202                     data = null;
203                 } else {
204                     data = "";
205                 }
206             }
207         }
208         return data == null ? null : data.trim();
209     }
210
211     private String getAdditionalData(XMLEvent event) throws XMLStreamException {
212         String data = "";
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());
220                 }
221             }
222         }
223         return data;
224     }
225
226     private String getLocalNameFor(StartElement startElement) {
227         return startElement.getName().getLocalPart();
228     }
229
230     private URI getNamespaceFor(StartElement startElement) {
231         String namespaceURI = startElement.getName().getNamespaceURI();
232         return namespaceURI.isEmpty() ? null : URI.create(namespaceURI);
233     }
234
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(
239                     startElement));
240             if (iiValue != null) {
241                 return iiValue;
242             }
243         }
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);
250             }
251         }
252         // it is not "prefix:value" but just "value"
253         return value;
254     }
255
256 }