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 com.google.common.base.Verify.verify;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.Strings;
14 import java.io.IOException;
17 import java.util.concurrent.ConcurrentHashMap;
18 import javax.xml.XMLConstants;
19 import javax.xml.namespace.NamespaceContext;
20 import javax.xml.stream.XMLStreamConstants;
21 import javax.xml.stream.XMLStreamException;
22 import javax.xml.stream.XMLStreamReader;
23 import javax.xml.stream.XMLStreamWriter;
24 import org.opendaylight.yangtools.yang.common.QName;
25 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
26 import org.opendaylight.yangtools.yang.data.util.SingleChildDataNodeContainer;
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 {
36 * Simple namespace/localname holder, an alternative to QName.
38 private static final class NSName {
39 private final String uri;
40 private final String name;
42 NSName(final String uri, final String name) {
43 this.uri = requireNonNull(uri);
44 this.name = requireNonNull(name);
48 private static final Logger LOG = LoggerFactory.getLogger(StreamWriterFacade.class);
49 private static final Set<String> BROKEN_NAMESPACES = ConcurrentHashMap.newKeySet();
50 private static final Set<String> LEGACY_ATTRIBUTES = ConcurrentHashMap.newKeySet();
52 private final XMLStreamWriter writer;
53 private final RandomPrefix prefixes;
55 // QName of an element we delayed emitting. This only happens if it is a naked element, without any attributes,
56 // namespace declarations or value.
57 private Object openElement;
59 StreamWriterFacade(final XMLStreamWriter writer) {
60 this.writer = requireNonNull(writer);
61 prefixes = new RandomPrefix(writer.getNamespaceContext());
64 void writeCharacters(final String text) throws XMLStreamException {
65 if (!Strings.isNullOrEmpty(text)) {
67 writer.writeCharacters(text);
72 void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
74 writer.writeNamespace(prefix, namespaceURI);
78 void writeAttribute(final String localName, final String value) throws XMLStreamException {
80 writer.writeAttribute(localName, value);
84 void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value)
85 throws XMLStreamException {
87 writer.writeAttribute(prefix, namespaceURI, localName, value);
91 NamespaceContext getNamespaceContext() {
92 // Accessing namespace context is okay, because a delayed element is known to have no effect on the result
93 return writer.getNamespaceContext();
96 private void flushElement() throws XMLStreamException {
97 if (openElement != null) {
99 final String localName;
100 if (openElement instanceof QName) {
101 final QName qname = (QName) openElement;
102 nsUri = qname.getNamespace().toString();
103 localName = qname.getLocalName();
105 verify(openElement instanceof NSName);
106 final NSName nsname = (NSName) openElement;
108 localName = nsname.name;
110 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, localName, nsUri);
115 void writeStartElement(final String namespace, final String localName) throws XMLStreamException {
118 final NamespaceContext context = writer.getNamespaceContext();
119 final boolean reuseNamespace;
120 if (context != null) {
121 reuseNamespace = namespace.equals(context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
123 reuseNamespace = XMLConstants.DEFAULT_NS_PREFIX.equals(writer.getPrefix(namespace));
126 if (!reuseNamespace) {
127 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, localName, namespace);
128 writer.writeDefaultNamespace(namespace);
130 openElement = new NSName(namespace, localName);
134 void writeStartElement(final QName qname) throws XMLStreamException {
137 final String namespace = qname.getNamespace().toString();
138 final NamespaceContext context = writer.getNamespaceContext();
139 final boolean reuseNamespace;
140 if (context != null) {
141 reuseNamespace = namespace.equals(context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
143 reuseNamespace = XMLConstants.DEFAULT_NS_PREFIX.equals(writer.getPrefix(namespace));
146 if (!reuseNamespace) {
147 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), namespace);
148 writer.writeDefaultNamespace(namespace);
154 void writeEndElement() throws XMLStreamException {
155 if (openElement != null) {
157 final String localName;
158 if (openElement instanceof QName) {
159 final QName qname = (QName) openElement;
160 nsUri = qname.getNamespace().toString();
161 localName = qname.getLocalName();
163 verify(openElement instanceof NSName);
164 final NSName nsname = (NSName) openElement;
166 localName = nsname.name;
169 writer.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, localName, nsUri);
172 writer.writeEndElement();
176 String getPrefix(final URI uri, final String str) throws XMLStreamException {
177 final String prefix = writer.getPrefix(str);
178 if (prefix != null) {
182 // This is needed to recover from attributes emitted while the namespace was not declared. Ordinarily
183 // attribute namespaces would be bound in the writer, so the resulting XML is efficient, but we cannot rely
184 // on that having been done.
185 if (BROKEN_NAMESPACES.add(str)) {
186 LOG.info("Namespace {} was not bound, please fix the caller", str, new Throwable());
189 return prefixes.encodePrefix(uri);
192 void close() throws XMLStreamException {
193 // Mighty careful stepping here, we must end up closing the writer
194 XMLStreamException failure = null;
197 } catch (XMLStreamException e) {
203 } catch (XMLStreamException e) {
204 if (failure != null) {
205 failure.addSuppressed(e);
213 void flush() throws XMLStreamException {
218 void anydataWriteStreamReader(final XMLStreamReader reader) throws XMLStreamException {
221 while (reader.hasNext()) {
222 final int event = reader.next();
224 case XMLStreamConstants.START_ELEMENT:
225 forwardStartElement(reader);
227 case XMLStreamConstants.END_ELEMENT:
228 writer.writeEndElement();
230 case XMLStreamConstants.CHARACTERS:
231 writer.writeCharacters(reader.getText());
233 case XMLStreamConstants.COMMENT:
234 writer.writeComment(reader.getText());
236 case XMLStreamConstants.SPACE:
237 // Ignore insignificant whitespace
239 case XMLStreamConstants.START_DOCUMENT:
240 case XMLStreamConstants.END_DOCUMENT:
241 // We are embedded: ignore start/end document events
243 case XMLStreamConstants.ATTRIBUTE:
244 forwardAttributes(reader);
246 case XMLStreamConstants.CDATA:
247 writer.writeCData(reader.getText());
249 case XMLStreamConstants.NAMESPACE:
250 forwardNamespaces(reader);
252 case XMLStreamConstants.DTD:
253 case XMLStreamConstants.NOTATION_DECLARATION:
254 case XMLStreamConstants.ENTITY_DECLARATION:
255 case XMLStreamConstants.ENTITY_REFERENCE:
256 case XMLStreamConstants.PROCESSING_INSTRUCTION:
258 throw new IllegalStateException("Unhandled event " + event);
263 void anyxmlWriteStreamReader(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
266 // Do not emit top-level element
268 while (reader.hasNext()) {
269 final int event = reader.next();
271 case XMLStreamConstants.START_ELEMENT:
273 forwardStartElement(reader);
275 forwardNamespaces(reader);
276 forwardAttributes(reader);
280 case XMLStreamConstants.END_ELEMENT:
282 writer.writeEndElement();
286 case XMLStreamConstants.PROCESSING_INSTRUCTION:
287 forwardProcessingInstruction(reader);
289 case XMLStreamConstants.CHARACTERS:
290 writer.writeCharacters(reader.getText());
292 case XMLStreamConstants.COMMENT:
293 writer.writeComment(reader.getText());
295 case XMLStreamConstants.SPACE:
296 // Ignore insignificant whitespace
298 case XMLStreamConstants.START_DOCUMENT:
299 case XMLStreamConstants.END_DOCUMENT:
300 // We are embedded: ignore start/end document events
302 case XMLStreamConstants.ENTITY_REFERENCE:
303 writer.writeEntityRef(reader.getLocalName());
305 case XMLStreamConstants.ATTRIBUTE:
306 forwardAttributes(reader);
308 case XMLStreamConstants.DTD:
309 writer.writeDTD(reader.getText());
311 case XMLStreamConstants.CDATA:
312 writer.writeCData(reader.getText());
314 case XMLStreamConstants.NAMESPACE:
315 forwardNamespaces(reader);
317 case XMLStreamConstants.NOTATION_DECLARATION:
318 case XMLStreamConstants.ENTITY_DECLARATION:
320 throw new IllegalStateException("Unhandled event " + event);
325 void emitNormalizedAnydata(final NormalizedAnydata anydata) throws XMLStreamException {
327 anydata.writeTo(XMLStreamNormalizedNodeStreamWriter.create(writer, anydata.getSchemaContext(),
328 new SingleChildDataNodeContainer(anydata.getContextNode())));
329 } catch (IOException e) {
330 throw new XMLStreamException("Failed to emit anydata " + anydata, e);
334 static void warnLegacyAttribute(final String localName) {
335 if (LEGACY_ATTRIBUTES.add(localName)) {
336 LOG.info("Encountered annotation {} not bound to module. Please examine the call stack and fix this "
337 + "warning by defining a proper YANG annotation to cover it", localName,
338 new Throwable("Call stack"));
342 private void forwardAttributes(final XMLStreamReader reader) throws XMLStreamException {
343 for (int i = 0, count = reader.getAttributeCount(); i < count; ++i) {
344 final String localName = reader.getAttributeLocalName(i);
345 final String value = reader.getAttributeValue(i);
346 final String prefix = reader.getAttributePrefix(i);
347 if (prefix != null) {
348 writer.writeAttribute(prefix, reader.getAttributeNamespace(i), localName, value);
350 writer.writeAttribute(localName, value);
355 private void forwardNamespaces(final XMLStreamReader reader) throws XMLStreamException {
356 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
357 writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
361 private void forwardProcessingInstruction(final XMLStreamReader reader) throws XMLStreamException {
362 final String target = reader.getPITarget();
363 final String data = reader.getPIData();
365 writer.writeProcessingInstruction(target, data);
367 writer.writeProcessingInstruction(target);
371 private void forwardStartElement(final XMLStreamReader reader) throws XMLStreamException {
372 final String localName = reader.getLocalName();
373 final String prefix = reader.getPrefix();
374 if (prefix != null) {
375 writer.writeStartElement(prefix, localName, reader.getNamespaceURI());
377 writer.writeStartElement(localName);
380 forwardNamespaces(reader);
381 forwardAttributes(reader);