Skip to content

Package: SaajStaxWriter$1$1

SaajStaxWriter$1$1

nameinstructionbranchcomplexitylinemethod
hasNext()
M: 7 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
next()
M: 15 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
remove()
M: 1 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
{...}
M: 16 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Distribution License v. 1.0, which is available at
6: * http://www.eclipse.org/org/documents/edl-v10.php.
7: *
8: * SPDX-License-Identifier: BSD-3-Clause
9: */
10:
11: package com.sun.xml.messaging.saaj.util.stax;
12:
13: import java.util.Iterator;
14: import java.util.Arrays;
15: import java.util.List;
16: import java.util.LinkedList;
17:
18: import javax.xml.namespace.NamespaceContext;
19: import javax.xml.namespace.QName;
20: import jakarta.xml.soap.SOAPElement;
21: import jakarta.xml.soap.SOAPException;
22: import jakarta.xml.soap.SOAPMessage;
23: import java.util.HashMap;
24: import java.util.Map;
25: import javax.xml.stream.XMLStreamException;
26: import javax.xml.stream.XMLStreamWriter;
27:
28: import org.w3c.dom.Comment;
29: import org.w3c.dom.Node;
30:
31: /**
32: * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface.
33: *
34: * <p>
35: * Defers creation of SOAPElement until all the aspects of the name of the element are known.
36: * In some cases, the namespace uri is indicated only by the {@link #writeNamespace(String, String)} call.
37: * After opening an element ({@code writeStartElement}, {@code writeEmptyElement} methods), all attributes
38: * and namespace assignments are retained within {@link DeferredElement} object ({@code deferredElement} field).
39: * As soon as any other method than {@code writeAttribute}, {@code writeNamespace}, {@code writeDefaultNamespace}
40: * or {@code setNamespace} is called, the contents of {@code deferredElement} is transformed into new SOAPElement
41: * (which is appropriately inserted into the SOAPMessage under construction).
42: * This mechanism is necessary to fix JDK-8159058 issue.
43: * </p>
44: *
45: * @author shih-chang.chen@oracle.com
46: */
47: public class SaajStaxWriter implements XMLStreamWriter {
48:
49: protected SOAPMessage soap;
50: protected String envURI;
51: protected SOAPElement currentElement;
52: protected DeferredElement deferredElement;
53:
54: static final protected String Envelope = "Envelope";
55: static final protected String Header = "Header";
56: static final protected String Body = "Body";
57: static final protected String xmlns = "xmlns";
58:
59: public SaajStaxWriter(final SOAPMessage msg, String uri) throws SOAPException {
60: soap = msg;
61: this.envURI = uri;
62: this.deferredElement = new DeferredElement();
63: }
64:
65: public SOAPMessage getSOAPMessage() {
66: return soap;
67: }
68:
69: protected SOAPElement getEnvelope() throws SOAPException {
70: return soap.getSOAPPart().getEnvelope();
71: }
72:
73: @Override
74: public void writeStartElement(final String localName) throws XMLStreamException {
75: currentElement = deferredElement.flushTo(currentElement);
76: deferredElement.setLocalName(localName);
77: }
78:
79: @Override
80: public void writeStartElement(final String ns, final String ln) throws XMLStreamException {
81: writeStartElement(null, ln, ns);
82: }
83:
84: @Override
85: public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException {
86: currentElement = deferredElement.flushTo(currentElement);
87:
88: if (envURI.equals(ns)) {
89: try {
90: if (Envelope.equals(ln)) {
91: currentElement = getEnvelope();
92: fixPrefix(prefix);
93: return;
94: } else if (Header.equals(ln)) {
95: currentElement = soap.getSOAPHeader();
96: fixPrefix(prefix);
97: return;
98: } else if (Body.equals(ln)) {
99: currentElement = soap.getSOAPBody();
100: fixPrefix(prefix);
101: return;
102: }
103: } catch (SOAPException e) {
104: throw new XMLStreamException(e);
105: }
106:
107: }
108:
109: deferredElement.setLocalName(ln);
110: deferredElement.setNamespaceUri(ns);
111: deferredElement.setPrefix(prefix);
112:
113: }
114:
115: private void fixPrefix(final String prfx) throws XMLStreamException {
116: fixPrefix(prfx, currentElement);
117: }
118:
119: private void fixPrefix(final String prfx, SOAPElement element) throws XMLStreamException {
120: String oldPrfx = element.getPrefix();
121: if (prfx != null && !prfx.equals(oldPrfx)) {
122: element.setPrefix(prfx);
123: }
124: }
125:
126: @Override
127: public void writeEmptyElement(final String uri, final String ln) throws XMLStreamException {
128: writeStartElement(null, ln, uri);
129: }
130:
131: @Override
132: public void writeEmptyElement(final String prefix, final String ln, final String uri) throws XMLStreamException {
133: writeStartElement(prefix, ln, uri);
134: }
135:
136: @Override
137: public void writeEmptyElement(final String ln) throws XMLStreamException {
138: writeStartElement(null, ln, null);
139: }
140:
141: @Override
142: public void writeEndElement() throws XMLStreamException {
143: currentElement = deferredElement.flushTo(currentElement);
144: if (currentElement != null) currentElement = currentElement.getParentElement();
145: }
146:
147: @Override
148: public void writeEndDocument() throws XMLStreamException {
149: currentElement = deferredElement.flushTo(currentElement);
150: }
151:
152: @Override
153: public void close() throws XMLStreamException {
154: }
155:
156: @Override
157: public void flush() throws XMLStreamException {
158: }
159:
160: @Override
161: public void writeAttribute(final String ln, final String val) throws XMLStreamException {
162: writeAttribute(null, null, ln, val);
163: }
164:
165: @Override
166: public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException {
167: if (ns == null && prefix == null && xmlns.equals(ln)) {
168: writeNamespace("", value);
169: } else {
170: if (deferredElement.isInitialized()) {
171: deferredElement.addAttribute(prefix, ns, ln, value);
172: } else {
173: addAttibuteToElement(currentElement, prefix, ns, ln, value);
174: }
175: }
176: }
177:
178: @Override
179: public void writeAttribute(final String ns, final String ln, final String val) throws XMLStreamException {
180: writeAttribute(null, ns, ln, val);
181: }
182:
183: @Override
184: public void writeNamespace(String prefix, final String uri) throws XMLStreamException {
185: // make prefix default if null or "xmlns" (according to javadoc)
186: String thePrefix = prefix == null || "xmlns".equals(prefix) ? "" : prefix;
187: if (deferredElement.isInitialized()) {
188: deferredElement.addNamespaceDeclaration(thePrefix, uri);
189: } else {
190: try {
191: currentElement.addNamespaceDeclaration(thePrefix, uri);
192: } catch (SOAPException e) {
193: throw new XMLStreamException(e);
194: }
195: }
196: }
197:
198: @Override
199: public void writeDefaultNamespace(final String uri) throws XMLStreamException {
200: writeNamespace("", uri);
201: }
202:
203: @Override
204: public void writeComment(final String data) throws XMLStreamException {
205: currentElement = deferredElement.flushTo(currentElement);
206: Comment c = soap.getSOAPPart().createComment(data);
207: currentElement.appendChild(c);
208: }
209:
210: @Override
211: public void writeProcessingInstruction(final String target) throws XMLStreamException {
212: currentElement = deferredElement.flushTo(currentElement);
213: Node n = soap.getSOAPPart().createProcessingInstruction(target, "");
214: currentElement.appendChild(n);
215: }
216:
217: @Override
218: public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException {
219: currentElement = deferredElement.flushTo(currentElement);
220: Node n = soap.getSOAPPart().createProcessingInstruction(target, data);
221: currentElement.appendChild(n);
222: }
223:
224: @Override
225: public void writeCData(final String data) throws XMLStreamException {
226: currentElement = deferredElement.flushTo(currentElement);
227: Node n = soap.getSOAPPart().createCDATASection(data);
228: currentElement.appendChild(n);
229: }
230:
231: @Override
232: public void writeDTD(final String dtd) throws XMLStreamException {
233: currentElement = deferredElement.flushTo(currentElement);
234: }
235:
236: @Override
237: public void writeEntityRef(final String name) throws XMLStreamException {
238: currentElement = deferredElement.flushTo(currentElement);
239: Node n = soap.getSOAPPart().createEntityReference(name);
240: currentElement.appendChild(n);
241: }
242:
243: @Override
244: public void writeStartDocument() throws XMLStreamException {
245: }
246:
247: @Override
248: public void writeStartDocument(final String version) throws XMLStreamException {
249: if (version != null) soap.getSOAPPart().setXmlVersion(version);
250: }
251:
252: @Override
253: public void writeStartDocument(final String encoding, final String version) throws XMLStreamException {
254: if (version != null) soap.getSOAPPart().setXmlVersion(version);
255: if (encoding != null) {
256: try {
257: soap.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, encoding);
258: } catch (SOAPException e) {
259: throw new XMLStreamException(e);
260: }
261: }
262: }
263:
264: @Override
265: public void writeCharacters(final String text) throws XMLStreamException {
266: currentElement = deferredElement.flushTo(currentElement);
267: try {
268: currentElement.addTextNode(text);
269: } catch (SOAPException e) {
270: throw new XMLStreamException(e);
271: }
272: }
273:
274: @Override
275: public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
276: currentElement = deferredElement.flushTo(currentElement);
277: char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len);
278: try {
279: currentElement.addTextNode(new String(chr));
280: } catch (SOAPException e) {
281: throw new XMLStreamException(e);
282: }
283: }
284:
285: @Override
286: public String getPrefix(final String uri) throws XMLStreamException {
287: return currentElement.lookupPrefix(uri);
288: }
289:
290: @Override
291: public void setPrefix(final String prefix, final String uri) throws XMLStreamException {
292: // TODO: this in fact is not what would be expected from XMLStreamWriter
293: // (e.g. XMLStreamWriter for writing to output stream does not write anything as result of
294: // this method, it just rememebers that given prefix is associated with the given uri
295: // for the scope; to actually declare the prefix assignment in the resulting XML, one
296: // needs to call writeNamespace(...) method
297: // Kept for backwards compatibility reasons - this might be worth of further investigation.
298: if (deferredElement.isInitialized()) {
299: deferredElement.addNamespaceDeclaration(prefix, uri);
300: } else {
301: throw new XMLStreamException("Namespace not associated with any element");
302: }
303: }
304:
305: @Override
306: public void setDefaultNamespace(final String uri) throws XMLStreamException {
307: setPrefix("", uri);
308: }
309:
310: @Override
311: public void setNamespaceContext(final NamespaceContext context)throws XMLStreamException {
312: throw new UnsupportedOperationException();
313: }
314:
315: @Override
316: public Object getProperty(final String name) throws IllegalArgumentException {
317: //TODO the following line is to make eclipselink happy ... they are aware of this problem -
318: if (javax.xml.stream.XMLOutputFactory.IS_REPAIRING_NAMESPACES.equals(name)) return Boolean.FALSE;
319: return null;
320: }
321:
322: @Override
323: public NamespaceContext getNamespaceContext() {
324: return new NamespaceContext() {
325: @Override
326: public String getNamespaceURI(final String prefix) {
327: return currentElement.getNamespaceURI(prefix);
328: }
329: @Override
330: public String getPrefix(final String namespaceURI) {
331: return currentElement.lookupPrefix(namespaceURI);
332: }
333: @Override
334: public Iterator<String> getPrefixes(final String namespaceURI) {
335: return new Iterator<String>() {
336: String prefix = getPrefix(namespaceURI);
337: @Override
338: public boolean hasNext() {
339:• return (prefix != null);
340: }
341: @Override
342: public String next() {
343:• if (!hasNext()) throw new java.util.NoSuchElementException();
344: String next = prefix;
345: prefix = null;
346: return next;
347: }
348: @Override
349: public void remove() {}
350: };
351: }
352: };
353: }
354:
355: static void addAttibuteToElement(SOAPElement element, String prefix, String ns, String ln, String value)
356: throws XMLStreamException {
357: try {
358: if (ns == null) {
359: element.setAttributeNS("", ln, value);
360: } else {
361: QName name = prefix == null ? new QName(ns, ln) : new QName(ns, ln, prefix);
362: element.addAttribute(name, value);
363: }
364: } catch (SOAPException e) {
365: throw new XMLStreamException(e);
366: }
367: }
368:
369: /**
370: * Holds details of element that needs to be deferred in order to manage namespace assignments correctly.
371: *
372: * <p>
373: * An instance of can be set with all the aspects of the element name (local name, prefix, namespace uri).
374: * Attributes and namespace declarations (special case of attribute) can be added.
375: * Namespace declarations are handled so that the element namespace is updated if it is implied by the namespace
376: * declaration and the namespace was not set to non-{@code null} value previously.
377: * </p>
378: *
379: * <p>
380: * The state of this object can be {@link #flushTo(SOAPElement) flushed} to SOAPElement - new SOAPElement will
381: * be added a child element; the new element will have exactly the shape as represented by the state of this
382: * object. Note that the {@link #flushTo(SOAPElement)} method does nothing
383: * (and returns the argument immediately) if the state of this object is not initialized
384: * (i.e. local name is null).
385: * </p>
386: *
387: * @author ondrej.cerny@oracle.com
388: */
389: static class DeferredElement {
390: private String prefix;
391: private String localName;
392: private String namespaceUri;
393: private final Map<String, String> namespaceDeclarations;
394: private final List<AttributeDeclaration> attributeDeclarations;
395:
396: DeferredElement() {
397: this.namespaceDeclarations = new HashMap<>();
398: this.attributeDeclarations = new LinkedList<>();
399: reset();
400: }
401:
402:
403: /**
404: * Set prefix of the element.
405: * @param prefix namespace prefix
406: */
407: public void setPrefix(final String prefix) {
408: this.prefix = prefix;
409: }
410:
411: /**
412: * Set local name of the element.
413: *
414: * <p>
415: * This method initializes the element.
416: * </p>
417: *
418: * @param localName local name {@code not null}
419: */
420: public void setLocalName(final String localName) {
421: if (localName == null) {
422: throw new IllegalArgumentException("localName can not be null");
423: }
424: this.localName = localName;
425: }
426:
427: /**
428: * Set namespace uri.
429: *
430: * @param namespaceUri namespace uri
431: */
432: public void setNamespaceUri(final String namespaceUri) {
433: this.namespaceUri = namespaceUri;
434: }
435:
436: /**
437: * Adds namespace prefix assignment to the element.
438: *
439: * @param prefix prefix (not {@code null})
440: * @param namespaceUri namespace uri
441: */
442: public void addNamespaceDeclaration(final String prefix, final String namespaceUri) {
443: if (null == this.namespaceUri && null != namespaceUri && emptyIfNull(this.prefix).equals(prefix)) {
444: this.namespaceUri = namespaceUri;
445: }
446: this.namespaceDeclarations.put(emptyIfNull(namespaceUri), prefix);
447: }
448:
449: /**
450: * Adds attribute to the element.
451: * @param prefix prefix
452: * @param ns namespace
453: * @param ln local name
454: * @param value value
455: */
456: public void addAttribute(final String prefix, final String ns, final String ln, final String value) {
457: if (ns == null && prefix == null && xmlns.equals(ln)) {
458: this.addNamespaceDeclaration(null, value);
459: } else {
460: this.attributeDeclarations.add(new AttributeDeclaration(emptyIfNull(prefix), ns, ln, value));
461: }
462: }
463:
464: /**
465: * Flushes state of this element to the {@code target} element.
466: *
467: * <p>
468: * If this element is initialized then it is added with all the namespace declarations and attributes
469: * to the {@code target} element as a child. The state of this element is reset to uninitialized.
470: * The newly added element object is returned.
471: * </p>
472: * <p>
473: * If this element is not initialized then the {@code target} is returned immediately, nothing else is done.
474: * </p>
475: *
476: * @param target target element
477: * @return {@code target} or new element
478: * @throws XMLStreamException on error
479: */
480: public SOAPElement flushTo(final SOAPElement target) throws XMLStreamException {
481: try {
482: if (this.localName != null) {
483: // add the element appropriately (based on namespace declaration)
484: final SOAPElement newElement;
485: if (this.namespaceUri == null) {
486: // add element with inherited scope
487: newElement = target.addChildElement(this.localName);
488: } else if (prefix == null) {
489: newElement = target.addChildElement(new QName(this.namespaceUri, this.localName));
490: } else {
491: newElement = target.addChildElement(this.localName, this.prefix, this.namespaceUri);
492: }
493: // add namespace declarations
494: for (Map.Entry<String, String> namespace : this.namespaceDeclarations.entrySet()) {
495: newElement.addNamespaceDeclaration(namespace.getValue(), namespace.getKey());
496: }
497: // add attribute declarations
498: for (AttributeDeclaration attribute : this.attributeDeclarations) {
499: String pfx = attribute.prefix;
500: if ("".equals(pfx)) {
501: String p = this.namespaceDeclarations.get(attribute.namespaceUri);
502: if (p != null) {
503: pfx = p;
504: }
505: }
506: addAttibuteToElement(newElement,
507: pfx, attribute.namespaceUri, attribute.localName, attribute.value);
508: }
509: // reset state
510: this.reset();
511:
512: return newElement;
513: } else {
514: return target;
515: }
516: // else after reset state -> not initialized
517: } catch (SOAPException e) {
518: throw new XMLStreamException(e);
519: }
520: }
521:
522: /**
523: * Is the element initialized?
524: * @return boolean indicating whether it was initialized after last flush
525: */
526: public boolean isInitialized() {
527: return this.localName != null;
528: }
529:
530: private void reset() {
531: this.localName = null;
532: this.prefix = null;
533: this.namespaceUri = null;
534: this.namespaceDeclarations.clear();
535: this.attributeDeclarations.clear();
536: }
537:
538: private static String emptyIfNull(String s) {
539: return s == null ? "" : s;
540: }
541: }
542:
543: static class AttributeDeclaration {
544: final String prefix;
545: final String namespaceUri;
546: final String localName;
547: final String value;
548:
549: AttributeDeclaration(String prefix, String namespaceUri, String localName, String value) {
550: this.prefix = prefix;
551: this.namespaceUri = namespaceUri;
552: this.localName = localName;
553: this.value = value;
554: }
555: }
556: }