Merge "BUG 1082 Migrate sal-rest-connector to Async Data Broker API"
[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 Node<?> read(InputStream entityStream) throws XMLStreamException,
37                                                                       UnsupportedFormatException,
38                                                                       IOException {
39         //Get an XML stream which can be marked, and reset, so we can check and 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         final Stack<NodeWrapper<?>> processingQueue = new Stack<>();
57         NodeWrapper<?> root = null;
58         NodeWrapper<?> element = null;
59         while (eventReader.hasNext()) {
60             final XMLEvent event = eventReader.nextEvent();
61
62             if (event.isStartElement()) {
63                 final StartElement startElement = event.asStartElement();
64                 CompositeNodeWrapper compParentNode = null;
65                 if (!processingQueue.isEmpty() && processingQueue.peek() instanceof CompositeNodeWrapper) {
66                     compParentNode = (CompositeNodeWrapper) processingQueue.peek();
67                 }
68                 NodeWrapper<?> newNode = null;
69                 if (isCompositeNodeEvent(event)) {
70                     newNode = resolveCompositeNodeFromStartElement(startElement);
71                     if (root == null) {
72                         root = newNode;
73                     }
74                 } else if (isSimpleNodeEvent(event)) {
75                     newNode = resolveSimpleNodeFromStartElement(startElement);
76                     if (root == null) {
77                         root = newNode;
78                     }
79                 }
80
81                 if (newNode != null) {
82                     processingQueue.push(newNode);
83                     if (compParentNode != null) {
84                         compParentNode.addValue(newNode);
85                     }
86                 }
87             } else if (event.isEndElement()) {
88                 element = processingQueue.pop();
89             }
90         }
91
92         if (!root.getLocalName().equals(element.getLocalName())) {
93             throw new UnsupportedFormatException("XML should contain only one root element");
94         }
95
96         return (Node<?>) root;
97     }
98
99     /**
100      * If the input stream is not markable, then it wraps the input stream with a buffered stream, which is mark able.
101      * That way we can check if the stream is empty safely.
102      *
103      * @param entityStream
104      * @return
105      */
106     private InputStream getMarkableStream(InputStream entityStream) {
107         if (!entityStream.markSupported()) {
108             entityStream = new BufferedInputStream(entityStream);
109         }
110         return entityStream;
111     }
112
113     private boolean isInputStreamEmpty(InputStream entityStream) throws IOException {
114         boolean isEmpty = false;
115         entityStream.mark(1);
116         if (entityStream.read() == -1) {
117             isEmpty = true;
118         }
119         entityStream.reset();
120         return isEmpty;
121     }
122
123     private boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException {
124         checkArgument(event != null, "XML Event cannot be NULL!");
125         if (event.isStartElement()) {
126             XMLEvent innerEvent = skipCommentsAndWhitespace();
127             if (innerEvent != null && (innerEvent.isCharacters() || innerEvent.isEndElement())) {
128                 return true;
129             }
130         }
131         return false;
132     }
133
134     private boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException {
135         checkArgument(event != null, "XML Event cannot be NULL!");
136         if (event.isStartElement()) {
137             XMLEvent innerEvent = skipCommentsAndWhitespace();
138             if (innerEvent != null) {
139                 if (innerEvent.isStartElement()) {
140                     return true;
141                 }
142             }
143         }
144         return false;
145     }
146
147     private XMLEvent skipCommentsAndWhitespace() throws XMLStreamException {
148         while (eventReader.hasNext()) {
149             XMLEvent event = eventReader.peek();
150             if (event.getEventType() == XMLStreamConstants.COMMENT) {
151                 eventReader.nextEvent();
152                 continue;
153             }
154
155             if (event.isCharacters()) {
156                 Characters chars = event.asCharacters();
157                 if (chars.isWhiteSpace()) {
158                     eventReader.nextEvent();
159                     continue;
160                 }
161             }
162             return event;
163         }
164         return null;
165     }
166
167     private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final StartElement startElement) {
168         checkArgument(startElement != null, "Start Element cannot be NULL!");
169         return new CompositeNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
170     }
171
172     private NodeWrapper<? extends Node<?>> resolveSimpleNodeFromStartElement(final StartElement startElement)
173             throws XMLStreamException {
174         checkArgument(startElement != null, "Start Element cannot be NULL!");
175         String data = getValueOf(startElement);
176         if (data == null) {
177             return new EmptyNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
178         }
179         return new SimpleNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement),
180                 resolveValueOfElement(data, startElement));
181     }
182
183     private String getValueOf(StartElement startElement) throws XMLStreamException {
184         String data = null;
185         if (eventReader.hasNext()) {
186             final XMLEvent innerEvent = eventReader.peek();
187             if (innerEvent.isCharacters()) {
188                 final Characters chars = innerEvent.asCharacters();
189                 if (!chars.isWhiteSpace()) {
190                     data = innerEvent.asCharacters().getData();
191                     data = data + getAdditionalData(eventReader.nextEvent());
192                 }
193             } else if (innerEvent.isEndElement()) {
194                 if (startElement.getLocation().getCharacterOffset() == innerEvent.getLocation().getCharacterOffset()) {
195                     data = null;
196                 } else {
197                     data = "";
198                 }
199             }
200         }
201         return data == null ? null : data.trim();
202     }
203
204     private String getAdditionalData(XMLEvent event) throws XMLStreamException {
205         String data = "";
206         if (eventReader.hasNext()) {
207             final XMLEvent innerEvent = eventReader.peek();
208             if (innerEvent.isCharacters() && !innerEvent.isEndElement()) {
209                 final Characters chars = innerEvent.asCharacters();
210                 if (!chars.isWhiteSpace()) {
211                     data = innerEvent.asCharacters().getData();
212                     data = data + getAdditionalData(eventReader.nextEvent());
213                 }
214             }
215         }
216         return data;
217     }
218
219     private String getLocalNameFor(StartElement startElement) {
220         return startElement.getName().getLocalPart();
221     }
222
223     private URI getNamespaceFor(StartElement startElement) {
224         String namespaceURI = startElement.getName().getNamespaceURI();
225         return namespaceURI.isEmpty() ? null : URI.create(namespaceURI);
226     }
227
228     private Object resolveValueOfElement(String value, StartElement startElement) {
229         // it could be instance-identifier Built-In Type
230         if (value.startsWith("/")) {
231             IdentityValuesDTO iiValue = RestUtil.asInstanceIdentifier(value, new RestUtil.PrefixMapingFromXml(
232                     startElement));
233             if (iiValue != null) {
234                 return iiValue;
235             }
236         }
237         // it could be identityref Built-In Type
238         String[] namespaceAndValue = value.split(":");
239         if (namespaceAndValue.length == 2) {
240             String namespace = startElement.getNamespaceContext().getNamespaceURI(namespaceAndValue[0]);
241             if (namespace != null && !namespace.isEmpty()) {
242                 return new IdentityValuesDTO(namespace, namespaceAndValue[1], namespaceAndValue[0], value);
243             }
244         }
245         // it is not "prefix:value" but just "value"
246         return value;
247     }
248
249 }