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;
15 import java.util.List;
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.common.XMLNamespace;
26 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedAnydata;
27 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
28 import org.opendaylight.yangtools.yang.model.api.EffectiveStatementInference;
29 import org.opendaylight.yangtools.yang.model.api.meta.EffectiveStatement;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
34 * The sole implementation of {@link ValueWriter}, tasked with synchronizing access to XMLStreamWriter state. The only
35 * class referencing this class should be {@link XMLStreamNormalizedNodeStreamWriter}.
37 final class StreamWriterFacade extends ValueWriter {
38 private static final Logger LOG = LoggerFactory.getLogger(StreamWriterFacade.class);
39 private static final Set<String> BROKEN_NAMESPACES = ConcurrentHashMap.newKeySet();
40 private static final Set<String> LEGACY_ATTRIBUTES = ConcurrentHashMap.newKeySet();
42 private final XMLStreamWriter writer;
43 private final RandomPrefix prefixes;
45 // QName of an element we delayed emitting. This only happens if it is a naked element, without any attributes,
46 // namespace declarations or value.
47 private QName openElement;
49 StreamWriterFacade(final XMLStreamWriter writer) {
50 this.writer = requireNonNull(writer);
51 prefixes = new RandomPrefix(writer.getNamespaceContext());
54 void writeCharacters(final String text) throws XMLStreamException {
55 if (!Strings.isNullOrEmpty(text)) {
57 writer.writeCharacters(text);
62 void writeNamespace(final String prefix, final String namespaceURI) throws XMLStreamException {
64 writer.writeNamespace(prefix, namespaceURI);
68 void writeAttribute(final String localName, final String value) throws XMLStreamException {
70 writer.writeAttribute(localName, value);
74 void writeAttribute(final String prefix, final String namespaceURI, final String localName, final String value)
75 throws XMLStreamException {
77 writer.writeAttribute(prefix, namespaceURI, localName, value);
81 NamespaceContext getNamespaceContext() {
82 // Accessing namespace context is okay, because a delayed element is known to have no effect on the result
83 return writer.getNamespaceContext();
86 private void flushElement() throws XMLStreamException {
87 if (openElement != null) {
88 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
89 openElement.getNamespace().toString());
94 void writeStartElement(final QName qname) throws XMLStreamException {
97 final String namespace = qname.getNamespace().toString();
98 final NamespaceContext context = writer.getNamespaceContext();
99 final boolean reuseNamespace;
100 if (context != null) {
101 reuseNamespace = namespace.equals(context.getNamespaceURI(XMLConstants.DEFAULT_NS_PREFIX));
103 reuseNamespace = XMLConstants.DEFAULT_NS_PREFIX.equals(writer.getPrefix(namespace));
106 if (!reuseNamespace) {
107 writer.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, qname.getLocalName(), namespace);
108 writer.writeDefaultNamespace(namespace);
114 void writeEndElement() throws XMLStreamException {
115 if (openElement != null) {
116 writer.writeEmptyElement(XMLConstants.DEFAULT_NS_PREFIX, openElement.getLocalName(),
117 openElement.getNamespace().toString());
120 writer.writeEndElement();
124 String getPrefix(final XMLNamespace uri, final String str) throws XMLStreamException {
125 final String prefix = writer.getPrefix(str);
126 if (prefix != null) {
130 // This is needed to recover from attributes emitted while the namespace was not declared. Ordinarily
131 // attribute namespaces would be bound in the writer, so the resulting XML is efficient, but we cannot rely
132 // on that having been done.
133 if (BROKEN_NAMESPACES.add(str)) {
134 LOG.info("Namespace {} was not bound, please fix the caller", str, new Throwable());
137 return prefixes.encodePrefix(uri);
140 void close() throws XMLStreamException {
141 // Mighty careful stepping here, we must end up closing the writer
142 XMLStreamException failure = null;
145 } catch (XMLStreamException e) {
151 } catch (XMLStreamException e) {
152 if (failure != null) {
153 failure.addSuppressed(e);
161 void flush() throws XMLStreamException {
166 void anydataWriteStreamReader(final XMLStreamReader reader) throws XMLStreamException {
169 // Do not emit top-level element
171 while (reader.hasNext()) {
172 final int event = reader.next();
174 case XMLStreamConstants.START_ELEMENT:
176 forwardStartElement(reader);
178 // anydata: forward namespaces only, skipping the default namespace
179 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
180 final String prefix = reader.getNamespacePrefix(i);
181 if (!XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) {
182 writer.writeNamespace(prefix, reader.getNamespaceURI(i));
188 case XMLStreamConstants.END_ELEMENT:
191 writer.writeEndElement();
194 case XMLStreamConstants.CHARACTERS:
195 writer.writeCharacters(reader.getText());
197 case XMLStreamConstants.COMMENT:
198 case XMLStreamConstants.SPACE:
199 // Ignore comments and insignificant whitespace
201 case XMLStreamConstants.START_DOCUMENT:
202 case XMLStreamConstants.END_DOCUMENT:
203 // We are embedded: ignore start/end document events
205 case XMLStreamConstants.ATTRIBUTE:
206 forwardAttributes(reader);
208 case XMLStreamConstants.CDATA:
209 writer.writeCData(reader.getText());
211 case XMLStreamConstants.NAMESPACE:
212 forwardNamespaces(reader);
214 case XMLStreamConstants.DTD:
215 case XMLStreamConstants.NOTATION_DECLARATION:
216 case XMLStreamConstants.ENTITY_DECLARATION:
217 case XMLStreamConstants.ENTITY_REFERENCE:
218 case XMLStreamConstants.PROCESSING_INSTRUCTION:
220 throw new IllegalStateException("Unhandled event " + event);
225 void anyxmlWriteStreamReader(final DOMSourceXMLStreamReader reader) throws XMLStreamException {
228 // Do not emit top-level element
230 while (reader.hasNext()) {
231 final int event = reader.next();
233 case XMLStreamConstants.START_ELEMENT:
235 forwardStartElement(reader);
237 forwardNamespaces(reader);
238 // anyxml, hence we need to forward attributes
239 forwardAttributes(reader);
243 case XMLStreamConstants.END_ELEMENT:
246 writer.writeEndElement();
249 case XMLStreamConstants.PROCESSING_INSTRUCTION:
250 forwardProcessingInstruction(reader);
252 case XMLStreamConstants.CHARACTERS:
253 writer.writeCharacters(reader.getText());
255 case XMLStreamConstants.COMMENT:
256 writer.writeComment(reader.getText());
258 case XMLStreamConstants.SPACE:
259 // Ignore insignificant whitespace
261 case XMLStreamConstants.START_DOCUMENT:
262 case XMLStreamConstants.END_DOCUMENT:
263 // We are embedded: ignore start/end document events
265 case XMLStreamConstants.ENTITY_REFERENCE:
266 writer.writeEntityRef(reader.getLocalName());
268 case XMLStreamConstants.ATTRIBUTE:
269 forwardAttributes(reader);
271 case XMLStreamConstants.DTD:
272 writer.writeDTD(reader.getText());
274 case XMLStreamConstants.CDATA:
275 writer.writeCData(reader.getText());
277 case XMLStreamConstants.NAMESPACE:
278 forwardNamespaces(reader);
280 case XMLStreamConstants.NOTATION_DECLARATION:
281 case XMLStreamConstants.ENTITY_DECLARATION:
283 throw new IllegalStateException("Unhandled event " + event);
288 void emitNormalizedAnydata(final NormalizedAnydata anydata) throws XMLStreamException {
291 final EffectiveStatementInference inference = anydata.getInference();
292 final List<? extends EffectiveStatement<?, ?>> path = inference.statementPath();
293 final DataNodeContainer parent;
294 if (path.size() > 1) {
295 final EffectiveStatement<?, ?> stmt = path.get(path.size() - 2);
296 verify(stmt instanceof DataNodeContainer, "Unexpected statement %s", stmt);
297 parent = (DataNodeContainer) stmt;
299 parent = inference.getEffectiveModelContext();
303 anydata.writeTo(XMLStreamNormalizedNodeStreamWriter.create(writer, inference.getEffectiveModelContext(),
305 } catch (IOException e) {
306 throw new XMLStreamException("Failed to emit anydata " + anydata, e);
310 static void warnLegacyAttribute(final String localName) {
311 if (LEGACY_ATTRIBUTES.add(localName)) {
312 LOG.info("Encountered annotation {} not bound to module. Please examine the call stack and fix this "
313 + "warning by defining a proper YANG annotation to cover it", localName,
314 new Throwable("Call stack"));
318 private void forwardAttributes(final XMLStreamReader reader) throws XMLStreamException {
319 for (int i = 0, count = reader.getAttributeCount(); i < count; ++i) {
320 final String localName = reader.getAttributeLocalName(i);
321 final String value = reader.getAttributeValue(i);
322 final String prefix = reader.getAttributePrefix(i);
323 if (prefix != null) {
324 writer.writeAttribute(prefix, reader.getAttributeNamespace(i), localName, value);
326 writer.writeAttribute(localName, value);
331 private void forwardNamespaces(final XMLStreamReader reader) throws XMLStreamException {
332 for (int i = 0; i < reader.getNamespaceCount(); ++i) {
333 writer.writeNamespace(reader.getNamespacePrefix(i), reader.getNamespaceURI(i));
337 private void forwardProcessingInstruction(final XMLStreamReader reader) throws XMLStreamException {
338 final String target = reader.getPITarget();
339 final String data = reader.getPIData();
341 writer.writeProcessingInstruction(target, data);
343 writer.writeProcessingInstruction(target);
347 private void forwardStartElement(final XMLStreamReader reader) throws XMLStreamException {
348 final String localName = reader.getLocalName();
349 final String prefix = reader.getPrefix();
350 if (prefix != null) {
351 writer.writeStartElement(prefix, localName, reader.getNamespaceURI());
353 writer.writeStartElement(localName);
356 forwardNamespaces(reader);
357 forwardAttributes(reader);