2 * Copyright (c) 2019 Pantheon Technologies, s.r.o. 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.yangtools.yang.data.codec.xml;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.Strings;
13 import java.io.IOException;
14 import java.util.NoSuchElementException;
16 import java.util.concurrent.ConcurrentHashMap;
17 import javax.xml.XMLConstants;
18 import javax.xml.namespace.NamespaceContext;
19 import javax.xml.stream.XMLStreamConstants;
20 import javax.xml.stream.XMLStreamException;
21 import javax.xml.stream.XMLStreamReader;
22 import javax.xml.stream.XMLStreamWriter;
23 import org.opendaylight.yangtools.yang.common.QName;
24 import org.opendaylight.yangtools.yang.common.XMLNamespace;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
26 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
31 * The sole implementation of {@link ValueWriter}, tasked with synchronizing access to XMLStreamWriter state. The only
32 * class referencing this class should be {@link XMLStreamNormalizedNodeStreamWriter}.
34 final class StreamWriterFacade extends ValueWriter {
35 private static final Logger LOG = LoggerFactory.getLogger(StreamWriterFacade.class);
36 private static final Set<String> BROKEN_NAMESPACES = ConcurrentHashMap.newKeySet();
37 private static final Set<String> LEGACY_ATTRIBUTES = ConcurrentHashMap.newKeySet();
39 private final XMLStreamWriter writer;
40 private final RandomPrefix prefixes;
42 // QName of an element we delayed emitting. This only happens if it is a naked element, without any attributes,
43 // namespace declarations or value.
44 private QName openElement;
46 StreamWriterFacade(final XMLStreamWriter writer) {
47 this.writer = requireNonNull(writer);
48 prefixes = new RandomPrefix(writer.getNamespaceContext());
51 void writeCharacters(final String text) throws XMLStreamException {
52 if (!Strings.isNullOrEmpty(text)) {
54 writer.writeCharacters(text);
59 void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
61 writer.writeNamespace(prefix, namespaceURI);
65 void writeAttribute(final String localName, final String value) throws XMLStreamException {
67 writer.writeAttribute(localName, value);
71 void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value)
72 throws XMLStreamException {
74 writer.writeAttribute(prefix, namespaceURI, localName, value);
78 NamespaceContext getNamespaceContext() {
79 // Accessing namespace context is okay, because a delayed element is known to have no effect on the result
80 return writer.getNamespaceContext();
83 private void flushElement() throws XMLStreamException {
84 if (openElement != null) {
85 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
86 openElement.getNamespace().toString());
91 void writeStartElement(final QName qname) throws XMLStreamException {
94 final String namespace = qname.getNamespace().toString();
95 final NamespaceContext context = writer.getNamespaceContext();
96 final boolean reuseNamespace;
97 if (context != null) {
98 reuseNamespace = namespace.equals(context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
100 reuseNamespace = XMLConstants.DEFAULT_NS_PREFIX.equals(writer.getPrefix(namespace));
103 if (!reuseNamespace) {
104 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), namespace);
105 writer.writeDefaultNamespace(namespace);
111 void writeEndElement() throws XMLStreamException {
112 if (openElement != null) {
113 writer.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
114 openElement.getNamespace().toString());
117 writer.writeEndElement();
121 String getPrefix(final XMLNamespace uri, final String str) throws XMLStreamException {
122 final String prefix = writer.getPrefix(str);
123 if (prefix != null) {
127 // This is needed to recover from attributes emitted while the namespace was not declared. Ordinarily
128 // attribute namespaces would be bound in the writer, so the resulting XML is efficient, but we cannot rely
129 // on that having been done.
130 if (BROKEN_NAMESPACES.add(str)) {
131 LOG.info("Namespace {} was not bound, please fix the caller", str, new Throwable());
134 return prefixes.encodePrefix(uri);
137 void close() throws XMLStreamException {
138 // Mighty careful stepping here, we must end up closing the writer
139 XMLStreamException failure = null;
142 } catch (XMLStreamException e) {
148 } catch (XMLStreamException e) {
149 if (failure != null) {
150 failure.addSuppressed(e);
158 void flush() throws XMLStreamException {
163 void anydataWriteStreamReader(final XMLStreamReader reader) throws XMLStreamException {
166 // Do not emit top-level element
168 while (reader.hasNext()) {
169 final int event = reader.next();
171 case XMLStreamConstants.START_ELEMENT:
173 forwardStartElement(reader);
175 // anydata: forward namespaces only, skipping the default namespace
176 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
177 final String prefix = reader.getNamespacePrefix(i);
178 if (!XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
179 writer.writeNamespace(prefix, reader.getNamespaceURI(i));
185 case XMLStreamConstants.END_ELEMENT:
188 writer.writeEndElement();
191 case XMLStreamConstants.CHARACTERS:
192 writer.writeCharacters(reader.getText());
194 case XMLStreamConstants.COMMENT:
195 case XMLStreamConstants.SPACE:
196 // Ignore comments and insignificant whitespace
198 case XMLStreamConstants.START_DOCUMENT:
199 case XMLStreamConstants.END_DOCUMENT:
200 // We are embedded: ignore start/end document events
202 case XMLStreamConstants.ATTRIBUTE:
203 forwardAttributes(reader);
205 case XMLStreamConstants.CDATA:
206 writer.writeCData(reader.getText());
208 case XMLStreamConstants.NAMESPACE:
209 forwardNamespaces(reader);
211 case XMLStreamConstants.DTD:
212 case XMLStreamConstants.NOTATION_DECLARATION:
213 case XMLStreamConstants.ENTITY_DECLARATION:
214 case XMLStreamConstants.ENTITY_REFERENCE:
215 case XMLStreamConstants.PROCESSING_INSTRUCTION:
217 throw new IllegalStateException("Unhandled event " + event);
222 void anyxmlWriteStreamReader(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
225 // Do not emit top-level element
227 while (reader.hasNext()) {
228 final int event = reader.next();
230 case XMLStreamConstants.START_ELEMENT:
232 forwardStartElement(reader);
234 forwardNamespaces(reader);
235 // anyxml, hence we need to forward attributes
236 forwardAttributes(reader);
240 case XMLStreamConstants.END_ELEMENT:
243 writer.writeEndElement();
246 case XMLStreamConstants.PROCESSING_INSTRUCTION:
247 forwardProcessingInstruction(reader);
249 case XMLStreamConstants.CHARACTERS:
250 writer.writeCharacters(reader.getText());
252 case XMLStreamConstants.COMMENT:
253 writer.writeComment(reader.getText());
255 case XMLStreamConstants.SPACE:
256 // Ignore insignificant whitespace
258 case XMLStreamConstants.START_DOCUMENT:
259 case XMLStreamConstants.END_DOCUMENT:
260 // We are embedded: ignore start/end document events
262 case XMLStreamConstants.ENTITY_REFERENCE:
263 writer.writeEntityRef(reader.getLocalName());
265 case XMLStreamConstants.ATTRIBUTE:
266 forwardAttributes(reader);
268 case XMLStreamConstants.DTD:
269 writer.writeDTD(reader.getText());
271 case XMLStreamConstants.CDATA:
272 writer.writeCData(reader.getText());
274 case XMLStreamConstants.NAMESPACE:
275 forwardNamespaces(reader);
277 case XMLStreamConstants.NOTATION_DECLARATION:
278 case XMLStreamConstants.ENTITY_DECLARATION:
280 throw new IllegalStateException("Unhandled event " + event);
285 void emitNormalizedAnydata(final NormalizedAnydata anydata) throws XMLStreamException {
288 // Adjust state to point to parent node and ensure it can handle data tree nodes
289 final SchemaInferenceStack.Inference inference;
291 final SchemaInferenceStack stack = SchemaInferenceStack.ofInference(anydata.getInference());
292 stack.exitToDataTree();
293 inference = stack.toInference();
294 } catch (IllegalArgumentException | IllegalStateException | NoSuchElementException e) {
295 throw new XMLStreamException("Cannot emit " + anydata, e);
299 anydata.writeTo(XMLStreamNormalizedNodeStreamWriter.create(writer, inference));
300 } catch (IOException e) {
301 throw new XMLStreamException("Failed to emit anydata " + anydata, e);
305 static void warnLegacyAttribute(final String localName) {
306 if (LEGACY_ATTRIBUTES.add(localName)) {
307 LOG.info("Encountered annotation {} not bound to module. Please examine the call stack and fix this "
308 + "warning by defining a proper YANG annotation to cover it", localName,
309 new Throwable("Call stack"));
313 private void forwardAttributes(final XMLStreamReader reader) throws XMLStreamException {
314 for (int i = 0, count = reader.getAttributeCount(); i < count; ++i) {
315 final String localName = reader.getAttributeLocalName(i);
316 final String value = reader.getAttributeValue(i);
317 final String prefix = reader.getAttributePrefix(i);
318 if (prefix != null) {
319 writer.writeAttribute(prefix, reader.getAttributeNamespace(i), localName, value);
321 writer.writeAttribute(localName, value);
326 private void forwardNamespaces(final XMLStreamReader reader) throws XMLStreamException {
327 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
328 writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
332 private void forwardProcessingInstruction(final XMLStreamReader reader) throws XMLStreamException {
333 final String target = reader.getPITarget();
334 final String data = reader.getPIData();
336 writer.writeProcessingInstruction(target, data);
338 writer.writeProcessingInstruction(target);
342 private void forwardStartElement(final XMLStreamReader reader) throws XMLStreamException {
343 final String localName = reader.getLocalName();
344 final String prefix = reader.getPrefix();
345 if (prefix != null) {
346 writer.writeStartElement(prefix, localName, reader.getNamespaceURI());
348 writer.writeStartElement(localName);
351 forwardNamespaces(reader);
352 forwardAttributes(reader);