2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.controller.sal.rest.impl;
10 import static com.google.common.base.Preconditions.checkArgument;
12 import java.io.BufferedInputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
16 import java.util.HashMap;
18 import java.util.Stack;
19 import javax.xml.stream.XMLEventReader;
20 import javax.xml.stream.XMLInputFactory;
21 import javax.xml.stream.XMLStreamConstants;
22 import javax.xml.stream.XMLStreamException;
23 import javax.xml.stream.events.Characters;
24 import javax.xml.stream.events.StartElement;
25 import javax.xml.stream.events.XMLEvent;
26 import org.opendaylight.controller.sal.restconf.impl.CompositeNodeWrapper;
27 import org.opendaylight.controller.sal.restconf.impl.IdentityValuesDTO;
28 import org.opendaylight.controller.sal.restconf.impl.InstanceIdWithSchemaNode;
29 import org.opendaylight.controller.sal.restconf.impl.NodeWrapper;
30 import org.opendaylight.controller.sal.restconf.impl.RestCodec;
31 import org.opendaylight.controller.sal.restconf.impl.RestconfDocumentedException;
32 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
33 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
34 import org.opendaylight.controller.sal.restconf.impl.SimpleNodeWrapper;
35 import org.opendaylight.yangtools.yang.common.QName;
36 import org.opendaylight.yangtools.yang.data.api.Node;
37 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
38 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
39 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
40 import org.opendaylight.yangtools.yang.model.api.LeafListSchemaNode;
41 import org.opendaylight.yangtools.yang.model.api.LeafSchemaNode;
42 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
43 import org.opendaylight.yangtools.yang.model.api.TypeDefinition;
45 public class XmlToNormalizedNodeReaderWithSchema {
47 private final static XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();
48 private XMLEventReader eventReader;
49 private InstanceIdWithSchemaNode iiWithSchema;
51 public XmlToNormalizedNodeReaderWithSchema(final InstanceIdWithSchemaNode iiWithSchema) {
52 this.iiWithSchema = iiWithSchema;
55 public Node<?> read(InputStream entityStream) throws XMLStreamException, UnsupportedFormatException, IOException {
56 // Get an XML stream which can be marked, and reset, so we can check and see if there is
57 // any content being provided.
58 entityStream = getMarkableStream(entityStream);
60 if (isInputStreamEmpty(entityStream)) {
64 eventReader = xmlInputFactory.createXMLEventReader(entityStream);
65 if (eventReader.hasNext()) {
66 XMLEvent element = eventReader.peek();
67 if (element.isStartDocument()) {
68 eventReader.nextEvent();
72 final Stack<NodeWrapper<?>> processingQueue = new Stack<>();
73 NodeWrapper<?> root = null;
74 NodeWrapper<?> element = null;
75 Stack<DataSchemaNode> processingQueueSchema = new Stack<>();
77 while (eventReader.hasNext()) {
78 final XMLEvent event = eventReader.nextEvent();
80 if (event.isStartElement()) {
81 final StartElement startElement = event.asStartElement();
82 CompositeNodeWrapper compParentNode = null;
83 if (!processingQueue.isEmpty() && processingQueue.peek() instanceof CompositeNodeWrapper) {
84 compParentNode = (CompositeNodeWrapper) processingQueue.peek();
85 findSchemaNodeForElement(startElement, processingQueueSchema);
87 processingQueueSchema = checkElementAndSchemaNodeNameAndNamespace(startElement,
88 iiWithSchema.getSchemaNode());
89 DataSchemaNode currentSchemaNode = processingQueueSchema.peek();
90 if (!(currentSchemaNode instanceof ListSchemaNode)
91 && !(currentSchemaNode instanceof ContainerSchemaNode)) {
92 throw new UnsupportedFormatException(
93 "Top level element has to be of type list or container schema node.");
97 NodeWrapper<?> newNode = null;
98 if (isCompositeNodeEvent(event)) {
99 newNode = resolveCompositeNodeFromStartElement(processingQueueSchema.peek().getQName());
103 } else if (isSimpleNodeEvent(event)) {
104 newNode = resolveSimpleNodeFromStartElement(processingQueueSchema.peek(), getValueOf(startElement));
110 if (newNode != null) {
111 processingQueue.push(newNode);
112 if (compParentNode != null) {
113 compParentNode.addValue(newNode);
116 } else if (event.isEndElement()) {
117 element = processingQueue.pop();
118 // if(((EndElement)event).getName().getLocalPart().equals
119 processingQueueSchema.pop();
123 if (!root.getLocalName().equals(element.getLocalName())) {
124 throw new UnsupportedFormatException("XML should contain only one root element");
127 return root.unwrap();
130 private void findSchemaNodeForElement(StartElement element, Stack<DataSchemaNode> processingQueueSchema) {
131 DataSchemaNode currentSchemaNode = processingQueueSchema.peek();
132 if (currentSchemaNode instanceof DataNodeContainer) {
133 final URI realNamespace = getNamespaceFor(element);
134 final String realName = getLocalNameFor(element);
135 Map<URI, DataSchemaNode> childNamesakes = resolveChildsWithNameAsElement(
136 ((DataNodeContainer) currentSchemaNode), realName);
137 DataSchemaNode childDataSchemaNode = childNamesakes.get(realNamespace);
138 if (childDataSchemaNode == null) {
139 throw new RestconfDocumentedException("Element " + realName + " has namespace " + realNamespace
140 + ". Available namespaces are: " + childNamesakes.keySet(), ErrorType.APPLICATION,
141 ErrorTag.INVALID_VALUE);
143 processingQueueSchema.push(childDataSchemaNode);
145 throw new RestconfDocumentedException("Element " + processingQueueSchema.peek().getQName().getLocalName()
146 + " should be data node container .", ErrorType.APPLICATION, ErrorTag.INVALID_VALUE);
152 * Returns map of data schema node which are accesible by URI which have equal name
154 private Map<URI, DataSchemaNode> resolveChildsWithNameAsElement(final DataNodeContainer dataNodeContainer,
155 final String realName) {
156 final Map<URI, DataSchemaNode> namespaceToDataSchemaNode = new HashMap<URI, DataSchemaNode>();
157 for (DataSchemaNode dataSchemaNode : dataNodeContainer.getChildNodes()) {
158 if (dataSchemaNode.equals(realName)) {
159 namespaceToDataSchemaNode.put(dataSchemaNode.getQName().getNamespace(), dataSchemaNode);
162 return namespaceToDataSchemaNode;
165 private final Stack<DataSchemaNode> checkElementAndSchemaNodeNameAndNamespace(final StartElement startElement,
166 final DataSchemaNode node) {
167 checkArgument(startElement != null, "Start Element cannot be NULL!");
168 final String expectedName = node.getQName().getLocalName();
169 final String xmlName = getLocalNameFor(startElement);
170 final URI expectedNamespace = node.getQName().getNamespace();
171 final URI xmlNamespace = getNamespaceFor(startElement);
172 if (!expectedName.equals(xmlName)) {
173 throw new RestconfDocumentedException("Xml element name: " + xmlName + "\nSchema node name: "
174 + expectedName, org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType.APPLICATION,
175 ErrorTag.INVALID_VALUE);
178 if (xmlNamespace != null && !expectedNamespace.equals(xmlNamespace)) {
179 throw new RestconfDocumentedException("Xml element ns: " + xmlNamespace + "\nSchema node ns: "
181 org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType.APPLICATION,
182 ErrorTag.INVALID_VALUE);
184 Stack<DataSchemaNode> processingQueueSchema = new Stack<>();
185 processingQueueSchema.push(node);
186 return processingQueueSchema;
190 * If the input stream is not markable, then it wraps the input stream with a buffered stream, which is mark able.
191 * That way we can check if the stream is empty safely.
193 * @param entityStream
196 private InputStream getMarkableStream(InputStream entityStream) {
197 if (!entityStream.markSupported()) {
198 entityStream = new BufferedInputStream(entityStream);
203 private boolean isInputStreamEmpty(final InputStream entityStream) throws IOException {
204 boolean isEmpty = false;
205 entityStream.mark(1);
206 if (entityStream.read() == -1) {
209 entityStream.reset();
213 private boolean isSimpleNodeEvent(final XMLEvent event) throws XMLStreamException {
214 checkArgument(event != null, "XML Event cannot be NULL!");
215 if (event.isStartElement()) {
216 XMLEvent innerEvent = skipCommentsAndWhitespace();
217 if (innerEvent != null && (innerEvent.isCharacters() || innerEvent.isEndElement())) {
224 private boolean isCompositeNodeEvent(final XMLEvent event) throws XMLStreamException {
225 checkArgument(event != null, "XML Event cannot be NULL!");
226 if (event.isStartElement()) {
227 XMLEvent innerEvent = skipCommentsAndWhitespace();
228 if (innerEvent != null) {
229 if (innerEvent.isStartElement()) {
237 private XMLEvent skipCommentsAndWhitespace() throws XMLStreamException {
238 while (eventReader.hasNext()) {
239 XMLEvent event = eventReader.peek();
240 if (event.getEventType() == XMLStreamConstants.COMMENT) {
241 eventReader.nextEvent();
245 if (event.isCharacters()) {
246 Characters chars = event.asCharacters();
247 if (chars.isWhiteSpace()) {
248 eventReader.nextEvent();
257 private CompositeNodeWrapper resolveCompositeNodeFromStartElement(final QName qName) {
258 // checkArgument(startElement != null, "Start Element cannot be NULL!");
259 CompositeNodeWrapper compositeNodeWrapper = new CompositeNodeWrapper("dummy");
260 compositeNodeWrapper.setQname(qName);
261 return compositeNodeWrapper;
265 private SimpleNodeWrapper resolveSimpleNodeFromStartElement(final DataSchemaNode node, final String value)
266 throws XMLStreamException {
267 // checkArgument(startElement != null, "Start Element cannot be NULL!");
268 Object deserializedValue = null;
270 if (node instanceof LeafSchemaNode) {
271 TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(((LeafSchemaNode) node).getType());
272 deserializedValue = RestCodec.from(baseType, iiWithSchema.getMountPoint()).deserialize(value);
273 } else if (node instanceof LeafListSchemaNode) {
274 TypeDefinition<?> baseType = RestUtil.resolveBaseTypeFrom(((LeafListSchemaNode) node).getType());
275 deserializedValue = RestCodec.from(baseType, iiWithSchema.getMountPoint()).deserialize(value);
278 // if (data == null) {
279 // return new EmptyNodeWrapper(getNamespaceFor(startElement), getLocalNameFor(startElement));
281 SimpleNodeWrapper simpleNodeWrapper = new SimpleNodeWrapper("dummy", deserializedValue);
282 simpleNodeWrapper.setQname(node.getQName());
283 return simpleNodeWrapper;
286 private String getValueOf(final StartElement startElement) throws XMLStreamException {
288 if (eventReader.hasNext()) {
289 final XMLEvent innerEvent = eventReader.peek();
290 if (innerEvent.isCharacters()) {
291 final Characters chars = innerEvent.asCharacters();
292 if (!chars.isWhiteSpace()) {
293 data = innerEvent.asCharacters().getData();
294 data = data + getAdditionalData(eventReader.nextEvent());
296 } else if (innerEvent.isEndElement()) {
297 if (startElement.getLocation().getCharacterOffset() == innerEvent.getLocation().getCharacterOffset()) {
304 return data == null ? null : data.trim();
307 private String getAdditionalData(final XMLEvent event) throws XMLStreamException {
309 if (eventReader.hasNext()) {
310 final XMLEvent innerEvent = eventReader.peek();
311 if (innerEvent.isCharacters() && !innerEvent.isEndElement()) {
312 final Characters chars = innerEvent.asCharacters();
313 if (!chars.isWhiteSpace()) {
314 data = innerEvent.asCharacters().getData();
315 data = data + getAdditionalData(eventReader.nextEvent());
322 private String getLocalNameFor(final StartElement startElement) {
323 return startElement.getName().getLocalPart();
326 private URI getNamespaceFor(final StartElement startElement) {
327 String namespaceURI = startElement.getName().getNamespaceURI();
328 return namespaceURI.isEmpty() ? null : URI.create(namespaceURI);
331 private Object resolveValueOfElement(final String value, final StartElement startElement) {
332 // it could be instance-identifier Built-In Type
333 if (value.startsWith("/")) {
334 IdentityValuesDTO iiValue = RestUtil.asInstanceIdentifier(value, new RestUtil.PrefixMapingFromXml(
336 if (iiValue != null) {
340 // it could be identityref Built-In Type
341 String[] namespaceAndValue = value.split(":");
342 if (namespaceAndValue.length == 2) {
343 String namespace = startElement.getNamespaceContext().getNamespaceURI(namespaceAndValue[0]);
344 if (namespace != null && !namespace.isEmpty()) {
345 return new IdentityValuesDTO(namespace, namespaceAndValue[1], namespaceAndValue[0], value);
348 // it is not "prefix:value" but just "value"