171805a1798a872ff7f42b821f3201dced670b9f
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / rest / impl / XmlReader.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
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;
24
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;
31
32 public class XmlReader {
33
34     private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
35     private XMLEventReader eventReader;
36
37     public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException,
38                                                                       UnsupportedFormatException,
39                                                                       IOException {
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);
43
44         if( isInputStreamEmpty( entityStream ) ) {
45             return null;
46         }
47
48         eventReader = xmlInputFactory.createXMLEventReader(entityStream);
49
50         if (eventReader.hasNext()) {
51             XMLEvent element = eventReader.peek();
52             if (element.isStartDocument()) {
53                 eventReader.nextEvent();
54             }
55         }
56
57         if (eventReader.hasNext() && !isCompositeNodeEvent(eventReader.peek())) {
58             throw new UnsupportedFormatException("Root element of XML has to be composite element.");
59         }
60
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();
66
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();
72                 }
73                 NodeWrapper<?> newNode = null;
74                 if (isCompositeNodeEvent(event)) {
75                     if (root == null) {
76                         root = resolveCompositeNodeFromStartElement(startElement);
77                         newNode = root;
78                     } else {
79                         newNode = resolveCompositeNodeFromStartElement(startElement);
80                     }
81                 } else if (isSimpleNodeEvent(event)) {
82                     if (root == null) {
83                         throw new UnsupportedFormatException("Root element of XML has to be composite element.");
84                     }
85                     newNode = resolveSimpleNodeFromStartElement(startElement);
86                 }
87
88                 if (newNode != null) {
89                     processingQueue.push(newNode);
90                     if (compParentNode != null) {
91                         compParentNode.addValue(newNode);
92                     }
93                 }
94             } else if (event.isEndElement()) {
95                 element = processingQueue.pop();
96             }
97         }
98
99         if (!root.getLocalName().equals(element.getLocalName())) {
100             throw new UnsupportedFormatException("XML should contain only one root element");
101         }
102
103         return root;
104     }
105
106     /**
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
110      * @return
111      */
112     private InputStream getMarkableStream(InputStream entityStream) {
113         if( !entityStream.markSupported() )
114         {
115             entityStream = new BufferedInputStream( entityStream );
116         }
117         return entityStream;
118     }
119
120     private boolean isInputStreamEmpty(InputStream entityStream)
121             throws IOException {
122         boolean isEmpty = false;
123         entityStream.mark( 1 );
124         if( entityStream.read() == -1 ){
125             isEmpty = true;
126         }
127         entityStream.reset();
128         return isEmpty;
129     }
130
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()) {
140                         return true;
141                     }
142                 } else if (innerEvent.isEndElement()) {
143                     return true;
144                 }
145             }
146         }
147         return false;
148     }
149
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()) {
154                 XMLEvent innerEvent;
155                 innerEvent = eventReader.peek();
156                 if (innerEvent.isCharacters()) {
157                     Characters chars = innerEvent.asCharacters();
158                     if (chars.isWhiteSpace()) {
159                         eventReader.nextEvent();
160                         innerEvent = eventReader.peek();
161                     }
162                 }
163                 if (innerEvent.isStartElement()) {
164                     return true;
165                 }
166             }
167         }
168         return false;
169     }
170
171     private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final StartElement startElement) {
172         checkArgument(startElement != null, "Start Element cannot be NULL!");
173         return new CompositeNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
174     }
175
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);
180         if (data == null) {
181             return new EmptyNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
182         }
183         return new SimpleNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement),
184                 resolveValueOfElement(data, startElement));
185     }
186
187     private String getValueOf(StartElement startElement) throws XMLStreamException {
188         String data = null;
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());
196                 }
197             } else if (innerEvent.isEndElement()) {
198                 if (startElement.getLocation().getCharacterOffset() == innerEvent.getLocation().getCharacterOffset()) {
199                     data = null;
200                 } else {
201                     data = "";
202                 }
203             }
204         }
205         return data == null ? null : data.trim();
206     }
207
208     private String getAdditionalData(XMLEvent event) throws XMLStreamException {
209         String data = "";
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());
217                 }
218             }
219         }
220         return data;
221     }
222
223     private String getLocalNameFor(StartElement startElement) {
224         return startElement.getName().getLocalPart();
225     }
226
227     private URI getNamespaceFor(StartElement startElement) {
228         String namespaceURI = startElement.getName().getNamespaceURI();
229         return namespaceURI.isEmpty() ? null : URI.create(namespaceURI);
230     }
231
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) {
237                 return iiValue;
238             }
239         }
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);
246             }
247         }
248         // it is not "prefix:value" but just "value"
249         return value;
250     }
251
252 }