Merge "Bug 809: Enhancements to the toaster example"
[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.InputStream;
13 import java.net.URI;
14 import java.util.Stack;
15
16 import javax.xml.stream.XMLEventReader;
17 import javax.xml.stream.XMLInputFactory;
18 import javax.xml.stream.XMLStreamException;
19 import javax.xml.stream.events.Characters;
20 import javax.xml.stream.events.StartElement;
21 import javax.xml.stream.events.XMLEvent;
22
23 import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
24 import org.opendaylight.controller.sal.restconf.impl.EmptyNodeWrapper;
25 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO;
26 import org.opendaylight.controller.sal.restconf.impl.NodeWrapper;
27 import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
28 import org.opendaylight.yangtools.yang.data.api.Node;
29
30 public class XmlReader {
31
32     private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
33     private XMLEventReader eventReader;
34
35     public CompositeNodeWrapper read(InputStream entityStream) throws XMLStreamException, UnsupportedFormatException {
36         eventReader = xmlInputFactory.createXMLEventReader(entityStream);
37
38         if (eventReader.hasNext()) {
39             XMLEvent element = eventReader.peek();
40             if (element.isStartDocument()) {
41                 eventReader.nextEvent();
42             }
43         }
44
45         if (eventReader.hasNext() && !isCompositeNodeEvent(eventReader.peek())) {
46             throw new UnsupportedFormatException("Root element of XML has to be composite element.");
47         }
48
49         final Stack<NodeWrapper<?>> processingQueue = new Stack<>();
50         CompositeNodeWrapper root = null;
51         NodeWrapper<?> element = null;
52         while (eventReader.hasNext()) {
53             final XMLEvent event = eventReader.nextEvent();
54
55             if (event.isStartElement()) {
56                 final StartElement startElement = event.asStartElement();
57                 CompositeNodeWrapper compParentNode = null;
58                 if (!processingQueue.isEmpty() && processingQueue.peek() instanceof CompositeNodeWrapper) {
59                     compParentNode = (CompositeNodeWrapper) processingQueue.peek();
60                 }
61                 NodeWrapper<?> newNode = null;
62                 if (isCompositeNodeEvent(event)) {
63                     if (root == null) {
64                         root = resolveCompositeNodeFromStartElement(startElement);
65                         newNode = root;
66                     } else {
67                         newNode = resolveCompositeNodeFromStartElement(startElement);
68                     }
69                 } else if (isSimpleNodeEvent(event)) {
70                     if (root == null) {
71                         throw new UnsupportedFormatException("Root element of XML has to be composite element.");
72                     }
73                     newNode = resolveSimpleNodeFromStartElement(startElement);
74                 }
75
76                 if (newNode != null) {
77                     processingQueue.push(newNode);
78                     if (compParentNode != null) {
79                         compParentNode.addValue(newNode);
80                     }
81                 }
82             } else if (event.isEndElement()) {
83                 element = processingQueue.pop();
84             }
85         }
86
87         if (!root.getLocalName().equals(element.getLocalName())) {
88             throw new UnsupportedFormatException("XML should contain only one root element");
89         }
90
91         return root;
92     }
93
94     private boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException {
95         checkArgument(event != null, "XML Event cannot be NULL!");
96         if (event.isStartElement()) {
97             if (eventReader.hasNext()) {
98                 final XMLEvent innerEvent;
99                 innerEvent = eventReader.peek();
100                 if (innerEvent.isCharacters()) {
101                     final Characters chars = innerEvent.asCharacters();
102                     if (!chars.isWhiteSpace()) {
103                         return true;
104                     }
105                 } else if (innerEvent.isEndElement()) {
106                     return true;
107                 }
108             }
109         }
110         return false;
111     }
112
113     private boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException {
114         checkArgument(event != null, "XML Event cannot be NULL!");
115         if (event.isStartElement()) {
116             if (eventReader.hasNext()) {
117                 XMLEvent innerEvent;
118                 innerEvent = eventReader.peek();
119                 if (innerEvent.isCharacters()) {
120                     Characters chars = innerEvent.asCharacters();
121                     if (chars.isWhiteSpace()) {
122                         eventReader.nextEvent();
123                         innerEvent = eventReader.peek();
124                     }
125                 }
126                 if (innerEvent.isStartElement()) {
127                     return true;
128                 }
129             }
130         }
131         return false;
132     }
133
134     private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final StartElement startElement) {
135         checkArgument(startElement != null, "Start Element cannot be NULL!");
136         return new CompositeNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
137     }
138
139     private NodeWrapper<? extends Node<?>> resolveSimpleNodeFromStartElement(final StartElement startElement)
140             throws XMLStreamException {
141         checkArgument(startElement != null, "Start Element cannot be NULL!");
142         String data = getValueOf(startElement);
143         if (data == null) {
144             return new EmptyNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
145         }
146         return new SimpleNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement),
147                 resolveValueOfElement(data, startElement));
148     }
149
150     private String getValueOf(StartElement startElement) throws XMLStreamException {
151         String data = null;
152         if (eventReader.hasNext()) {
153             final XMLEvent innerEvent = eventReader.peek();
154             if (innerEvent.isCharacters()) {
155                 final Characters chars = innerEvent.asCharacters();
156                 if (!chars.isWhiteSpace()) {
157                     data = innerEvent.asCharacters().getData();
158                     data = data + getAdditionalData(eventReader.nextEvent());
159                 }
160             } else if (innerEvent.isEndElement()) {
161                 if (startElement.getLocation().getCharacterOffset() == innerEvent.getLocation().getCharacterOffset()) {
162                     data = null;
163                 } else {
164                     data = "";
165                 }
166             }
167         }
168         return data;
169     }
170
171     private String getAdditionalData(XMLEvent event) throws XMLStreamException {
172         String data = "";
173         if (eventReader.hasNext()) {
174             final XMLEvent innerEvent = eventReader.peek();
175             if (innerEvent.isCharacters() && !innerEvent.isEndElement()) {
176                 final Characters chars = innerEvent.asCharacters();
177                 if (!chars.isWhiteSpace()) {
178                     data = innerEvent.asCharacters().getData();
179                     data = data + getAdditionalData(eventReader.nextEvent());
180                 }
181             }
182         }
183         return data;
184     }
185
186     private String getLocalNameFor(StartElement startElement) {
187         return startElement.getName().getLocalPart();
188     }
189
190     private URI getNamespaceFor(StartElement startElement) {
191         String namespaceURI = startElement.getName().getNamespaceURI();
192         return namespaceURI.isEmpty() ? null : URI.create(namespaceURI);
193     }
194
195     private Object resolveValueOfElement(String value, StartElement startElement) {
196         // it could be instance-identifier Built-In Type
197         if (value.startsWith("/")) {
198             IdentityValuesDTO iiValue = RestUtil.asInstanceIdentifier(value, new RestUtil.PrefixMapingFromXml(startElement));
199             if (iiValue != null) {
200                 return iiValue;
201             }
202         }
203         // it could be identityref Built-In Type
204         String[] namespaceAndValue = value.split(":");
205         if (namespaceAndValue.length == 2) {
206             String namespace = startElement.getNamespaceContext().getNamespaceURI(namespaceAndValue[0]);
207             if (namespace != null && !namespace.isEmpty()) {
208                 return new IdentityValuesDTO(namespace, namespaceAndValue[1], namespaceAndValue[0],value);
209             }
210         }
211         // it is not "prefix:value" but just "value"
212         return value;
213     }
214
215 }