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.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.common.XMLNamespace;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
27 import org.opendaylight.yangtools.yang.model.util.SchemaInferenceStack;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
32 * The sole implementation of {@link ValueWriter}, tasked with synchronizing access to XMLStreamWriter state. The only
33 * class referencing this class should be {@link XMLStreamNormalizedNodeStreamWriter}.
35 final class StreamWriterFacade extends ValueWriter {
36 private static final Logger LOG = LoggerFactory.getLogger(StreamWriterFacade.class);
37 private static final Set<String> BROKEN_NAMESPACES = ConcurrentHashMap.newKeySet();
38 private static final Set<String> LEGACY_ATTRIBUTES = ConcurrentHashMap.newKeySet();
40 private final XMLStreamWriter writer;
41 private final NamespacePrefixes prefixes;
43 // QName of an element we delayed emitting. This only happens if it is a naked element, without any attributes,
44 // namespace declarations or value.
45 private QName openElement;
47 StreamWriterFacade(final XMLStreamWriter writer, final @Nullable PreferredPrefixes pref) {
48 this.writer = requireNonNull(writer);
49 prefixes = new NamespacePrefixes(writer.getNamespaceContext(), pref);
52 void writeCharacters(final String text) throws XMLStreamException {
53 if (!Strings.isNullOrEmpty(text)) {
55 writer.writeCharacters(text);
60 void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
62 writer.writeNamespace(prefix, namespaceURI);
66 void writeAttribute(final String localName, final String value) throws XMLStreamException {
68 writer.writeAttribute(localName, value);
72 void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value)
73 throws XMLStreamException {
75 writer.writeAttribute(prefix, namespaceURI, localName, value);
79 NamespaceContext getNamespaceContext() {
80 // Accessing namespace context is okay, because a delayed element is known to have no effect on the result
81 return writer.getNamespaceContext();
84 private void flushElement() throws XMLStreamException {
85 if (openElement != null) {
86 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
87 openElement.getNamespace().toString());
92 void writeStartElement(final QName qname) throws XMLStreamException {
95 final String namespace = qname.getNamespace().toString();
96 final NamespaceContext context = writer.getNamespaceContext();
97 final boolean reuseNamespace;
98 if (context != null) {
99 reuseNamespace = namespace.equals(context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
101 reuseNamespace = XMLConstants.DEFAULT_NS_PREFIX.equals(writer.getPrefix(namespace));
104 if (!reuseNamespace) {
105 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), namespace);
106 writer.writeDefaultNamespace(namespace);
112 void writeEndElement() throws XMLStreamException {
113 if (openElement != null) {
114 writer.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
115 openElement.getNamespace().toString());
118 writer.writeEndElement();
122 String getPrefix(final XMLNamespace uri, final String str) throws XMLStreamException {
123 final String prefix = writer.getPrefix(str);
124 if (prefix != null) {
128 // This is needed to recover from attributes emitted while the namespace was not declared. Ordinarily
129 // attribute namespaces would be bound in the writer, so the resulting XML is efficient, but we cannot rely
130 // on that having been done.
131 if (BROKEN_NAMESPACES.add(str)) {
132 LOG.info("Namespace {} was not bound, please fix the caller", str, new Throwable());
135 return prefixes.encodePrefix(uri);
138 void close() throws XMLStreamException {
139 // Mighty careful stepping here, we must end up closing the writer
140 XMLStreamException failure = null;
143 } catch (XMLStreamException e) {
149 } catch (XMLStreamException e) {
150 if (failure != null) {
151 failure.addSuppressed(e);
159 void flush() throws XMLStreamException {
164 void anydataWriteStreamReader(final XMLStreamReader reader) throws XMLStreamException {
167 // Do not emit top-level element
169 while (reader.hasNext()) {
170 final int event = reader.next();
172 case XMLStreamConstants.START_ELEMENT:
174 forwardStartElement(reader);
176 // anydata: forward namespaces only, skipping the default namespace
177 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
178 final String prefix = reader.getNamespacePrefix(i);
179 if (!XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
180 writer.writeNamespace(prefix, reader.getNamespaceURI(i));
186 case XMLStreamConstants.END_ELEMENT:
189 writer.writeEndElement();
192 case XMLStreamConstants.CHARACTERS:
193 writer.writeCharacters(reader.getText());
195 case XMLStreamConstants.COMMENT:
196 case XMLStreamConstants.SPACE:
197 // Ignore comments and insignificant whitespace
199 case XMLStreamConstants.START_DOCUMENT:
200 case XMLStreamConstants.END_DOCUMENT:
201 // We are embedded: ignore start/end document events
203 case XMLStreamConstants.ATTRIBUTE:
204 forwardAttributes(reader);
206 case XMLStreamConstants.CDATA:
207 writer.writeCData(reader.getText());
209 case XMLStreamConstants.NAMESPACE:
210 forwardNamespaces(reader);
212 case XMLStreamConstants.DTD:
213 case XMLStreamConstants.NOTATION_DECLARATION:
214 case XMLStreamConstants.ENTITY_DECLARATION:
215 case XMLStreamConstants.ENTITY_REFERENCE:
216 case XMLStreamConstants.PROCESSING_INSTRUCTION:
218 throw new IllegalStateException("Unhandled event " + event);
223 void anyxmlWriteStreamReader(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
226 // Do not emit top-level element
228 while (reader.hasNext()) {
229 final int event = reader.next();
231 case XMLStreamConstants.START_ELEMENT:
233 forwardStartElement(reader);
235 forwardNamespaces(reader);
236 // anyxml, hence we need to forward attributes
237 forwardAttributes(reader);
241 case XMLStreamConstants.END_ELEMENT:
244 writer.writeEndElement();
247 case XMLStreamConstants.PROCESSING_INSTRUCTION:
248 forwardProcessingInstruction(reader);
250 case XMLStreamConstants.CHARACTERS:
251 writer.writeCharacters(reader.getText());
253 case XMLStreamConstants.COMMENT:
254 writer.writeComment(reader.getText());
256 case XMLStreamConstants.SPACE:
257 // Ignore insignificant whitespace
259 case XMLStreamConstants.START_DOCUMENT:
260 case XMLStreamConstants.END_DOCUMENT:
261 // We are embedded: ignore start/end document events
263 case XMLStreamConstants.ENTITY_REFERENCE:
264 writer.writeEntityRef(reader.getLocalName());
266 case XMLStreamConstants.ATTRIBUTE:
267 forwardAttributes(reader);
269 case XMLStreamConstants.DTD:
270 writer.writeDTD(reader.getText());
272 case XMLStreamConstants.CDATA:
273 writer.writeCData(reader.getText());
275 case XMLStreamConstants.NAMESPACE:
276 forwardNamespaces(reader);
278 case XMLStreamConstants.NOTATION_DECLARATION:
279 case XMLStreamConstants.ENTITY_DECLARATION:
281 throw new IllegalStateException("Unhandled event " + event);
286 void emitNormalizedAnydata(final NormalizedAnydata anydata) throws XMLStreamException {
289 // Adjust state to point to parent node and ensure it can handle data tree nodes
290 final SchemaInferenceStack.Inference inference;
292 final SchemaInferenceStack stack = SchemaInferenceStack.ofInference(anydata.getInference());
293 stack.exitToDataTree();
294 inference = stack.toInference();
295 } catch (IllegalArgumentException | IllegalStateException | NoSuchElementException e) {
296 throw new XMLStreamException("Cannot emit " + anydata, e);
300 anydata.writeTo(XMLStreamNormalizedNodeStreamWriter.create(writer, inference));
301 } catch (IOException e) {
302 throw new XMLStreamException("Failed to emit anydata " + anydata, e);
306 static void warnLegacyAttribute(final String localName) {
307 if (LEGACY_ATTRIBUTES.add(localName)) {
308 LOG.info("Encountered annotation {} not bound to module. Please examine the call stack and fix this "
309 + "warning by defining a proper YANG annotation to cover it", localName,
310 new Throwable("Call stack"));
314 private void forwardAttributes(final XMLStreamReader reader) throws XMLStreamException {
315 for (int i = 0, count = reader.getAttributeCount(); i < count; ++i) {
316 final String localName = reader.getAttributeLocalName(i);
317 final String value = reader.getAttributeValue(i);
318 final String prefix = reader.getAttributePrefix(i);
319 if (prefix != null) {
320 writer.writeAttribute(prefix, reader.getAttributeNamespace(i), localName, value);
322 writer.writeAttribute(localName, value);
327 private void forwardNamespaces(final XMLStreamReader reader) throws XMLStreamException {
328 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
329 writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
333 private void forwardProcessingInstruction(final XMLStreamReader reader) throws XMLStreamException {
334 final String target = reader.getPITarget();
335 final String data = reader.getPIData();
337 writer.writeProcessingInstruction(target, data);
339 writer.writeProcessingInstruction(target);
343 private void forwardStartElement(final XMLStreamReader reader) throws XMLStreamException {
344 final String localName = reader.getLocalName();
345 final String prefix = reader.getPrefix();
346 if (prefix != null) {
347 writer.writeStartElement(prefix, localName, reader.getNamespaceURI());
349 writer.writeStartElement(localName);
352 forwardNamespaces(reader);
353 forwardAttributes(reader);