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