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;
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.data.api.schema.NormalizedAnydata;
25 import org.opendaylight.yangtools.yang.data.util.SingleChildDataNodeContainer;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
30 * The sole implementation of {@link ValueWriter}, tasked with synchronizing access to XMLStreamWriter state. The only
31 * class referencing this class should be {@link XMLStreamNormalizedNodeStreamWriter}.
33 final class StreamWriterFacade extends ValueWriter {
34 private static final Logger LOG = LoggerFactory.getLogger(StreamWriterFacade.class);
35 private static final Set<String> BROKEN_NAMESPACES = ConcurrentHashMap.newKeySet();
36 private static final Set<String> LEGACY_ATTRIBUTES = ConcurrentHashMap.newKeySet();
38 private final XMLStreamWriter writer;
39 private final RandomPrefix prefixes;
41 // QName of an element we delayed emitting. This only happens if it is a naked element, without any attributes,
42 // namespace declarations or value.
43 private QName openElement;
45 StreamWriterFacade(final XMLStreamWriter writer) {
46 this.writer = requireNonNull(writer);
47 prefixes = new RandomPrefix(writer.getNamespaceContext());
50 void writeCharacters(final String text) throws XMLStreamException {
51 if (!Strings.isNullOrEmpty(text)) {
53 writer.writeCharacters(text);
58 void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
60 writer.writeNamespace(prefix, namespaceURI);
64 void writeAttribute(final String localName, final String value) throws XMLStreamException {
66 writer.writeAttribute(localName, value);
70 void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value)
71 throws XMLStreamException {
73 writer.writeAttribute(prefix, namespaceURI, localName, value);
77 NamespaceContext getNamespaceContext() {
78 // Accessing namespace context is okay, because a delayed element is known to have no effect on the result
79 return writer.getNamespaceContext();
82 private void flushElement() throws XMLStreamException {
83 if (openElement != null) {
84 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
85 openElement.getNamespace().toString());
90 void writeStartElement(final QName qname) throws XMLStreamException {
93 final String namespace = qname.getNamespace().toString();
94 final NamespaceContext context = writer.getNamespaceContext();
95 final boolean reuseNamespace;
96 if (context != null) {
97 reuseNamespace = namespace.equals(context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
99 reuseNamespace = XMLConstants.DEFAULT_NS_PREFIX.equals(writer.getPrefix(namespace));
102 if (!reuseNamespace) {
103 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), namespace);
104 writer.writeDefaultNamespace(namespace);
110 void writeEndElement() throws XMLStreamException {
111 if (openElement != null) {
112 writer.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
113 openElement.getNamespace().toString());
116 writer.writeEndElement();
120 String getPrefix(final URI uri, final String str) throws XMLStreamException {
121 final String prefix = writer.getPrefix(str);
122 if (prefix != null) {
126 // This is needed to recover from attributes emitted while the namespace was not declared. Ordinarily
127 // attribute namespaces would be bound in the writer, so the resulting XML is efficient, but we cannot rely
128 // on that having been done.
129 if (BROKEN_NAMESPACES.add(str)) {
130 LOG.info("Namespace {} was not bound, please fix the caller", str, new Throwable());
133 return prefixes.encodePrefix(uri);
136 void close() throws XMLStreamException {
137 // Mighty careful stepping here, we must end up closing the writer
138 XMLStreamException failure = null;
141 } catch (XMLStreamException e) {
147 } catch (XMLStreamException e) {
148 if (failure != null) {
149 failure.addSuppressed(e);
157 void flush() throws XMLStreamException {
162 void anydataWriteStreamReader(final XMLStreamReader reader) throws XMLStreamException {
165 // Do not emit top-level element
167 while (reader.hasNext()) {
168 final int event = reader.next();
170 case XMLStreamConstants.START_ELEMENT:
172 forwardStartElement(reader);
174 // anydata: forward namespaces only, skipping the default namespace
175 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
176 final String prefix = reader.getNamespacePrefix(i);
177 if (!XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
178 writer.writeNamespace(prefix, reader.getNamespaceURI(i));
184 case XMLStreamConstants.END_ELEMENT:
187 writer.writeEndElement();
190 case XMLStreamConstants.CHARACTERS:
191 writer.writeCharacters(reader.getText());
193 case XMLStreamConstants.COMMENT:
194 case XMLStreamConstants.SPACE:
195 // Ignore comments and insignificant whitespace
197 case XMLStreamConstants.START_DOCUMENT:
198 case XMLStreamConstants.END_DOCUMENT:
199 // We are embedded: ignore start/end document events
201 case XMLStreamConstants.ATTRIBUTE:
202 forwardAttributes(reader);
204 case XMLStreamConstants.CDATA:
205 writer.writeCData(reader.getText());
207 case XMLStreamConstants.NAMESPACE:
208 forwardNamespaces(reader);
210 case XMLStreamConstants.DTD:
211 case XMLStreamConstants.NOTATION_DECLARATION:
212 case XMLStreamConstants.ENTITY_DECLARATION:
213 case XMLStreamConstants.ENTITY_REFERENCE:
214 case XMLStreamConstants.PROCESSING_INSTRUCTION:
216 throw new IllegalStateException("Unhandled event " + event);
221 void anyxmlWriteStreamReader(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
224 // Do not emit top-level element
226 while (reader.hasNext()) {
227 final int event = reader.next();
229 case XMLStreamConstants.START_ELEMENT:
231 forwardStartElement(reader);
233 forwardNamespaces(reader);
234 // anyxml, hence we need to forward attributes
235 forwardAttributes(reader);
239 case XMLStreamConstants.END_ELEMENT:
242 writer.writeEndElement();
245 case XMLStreamConstants.PROCESSING_INSTRUCTION:
246 forwardProcessingInstruction(reader);
248 case XMLStreamConstants.CHARACTERS:
249 writer.writeCharacters(reader.getText());
251 case XMLStreamConstants.COMMENT:
252 writer.writeComment(reader.getText());
254 case XMLStreamConstants.SPACE:
255 // Ignore insignificant whitespace
257 case XMLStreamConstants.START_DOCUMENT:
258 case XMLStreamConstants.END_DOCUMENT:
259 // We are embedded: ignore start/end document events
261 case XMLStreamConstants.ENTITY_REFERENCE:
262 writer.writeEntityRef(reader.getLocalName());
264 case XMLStreamConstants.ATTRIBUTE:
265 forwardAttributes(reader);
267 case XMLStreamConstants.DTD:
268 writer.writeDTD(reader.getText());
270 case XMLStreamConstants.CDATA:
271 writer.writeCData(reader.getText());
273 case XMLStreamConstants.NAMESPACE:
274 forwardNamespaces(reader);
276 case XMLStreamConstants.NOTATION_DECLARATION:
277 case XMLStreamConstants.ENTITY_DECLARATION:
279 throw new IllegalStateException("Unhandled event " + event);
284 void emitNormalizedAnydata(final NormalizedAnydata anydata) throws XMLStreamException {
287 anydata.writeTo(XMLStreamNormalizedNodeStreamWriter.create(writer, anydata.getSchemaContext(),
288 new SingleChildDataNodeContainer(anydata.getContextNode())));
289 } catch (IOException e) {
290 throw new XMLStreamException("Failed to emit anydata " + anydata, e);
294 static void warnLegacyAttribute(final String localName) {
295 if (LEGACY_ATTRIBUTES.add(localName)) {
296 LOG.info("Encountered annotation {} not bound to module. Please examine the call stack and fix this "
297 + "warning by defining a proper YANG annotation to cover it", localName,
298 new Throwable("Call stack"));
302 private void forwardAttributes(final XMLStreamReader reader) throws XMLStreamException {
303 for (int i = 0, count = reader.getAttributeCount(); i < count; ++i) {
304 final String localName = reader.getAttributeLocalName(i);
305 final String value = reader.getAttributeValue(i);
306 final String prefix = reader.getAttributePrefix(i);
307 if (prefix != null) {
308 writer.writeAttribute(prefix, reader.getAttributeNamespace(i), localName, value);
310 writer.writeAttribute(localName, value);
315 private void forwardNamespaces(final XMLStreamReader reader) throws XMLStreamException {
316 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
317 writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
321 private void forwardProcessingInstruction(final XMLStreamReader reader) throws XMLStreamException {
322 final String target = reader.getPITarget();
323 final String data = reader.getPIData();
325 writer.writeProcessingInstruction(target, data);
327 writer.writeProcessingInstruction(target);
331 private void forwardStartElement(final XMLStreamReader reader) throws XMLStreamException {
332 final String localName = reader.getLocalName();
333 final String prefix = reader.getPrefix();
334 if (prefix != null) {
335 writer.writeStartElement(prefix, localName, reader.getNamespaceURI());
337 writer.writeStartElement(localName);
340 forwardNamespaces(reader);
341 forwardAttributes(reader);