src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java

changeset 1460
c946a5cc042f
parent 1341
e5cc521294d8
child 1550
c4309a2d981b
equal deleted inserted replaced
1459:4f8946adc54d 1460:c946a5cc042f
1 /* 1 /*
2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 2 * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 * 4 *
5 * This code is free software; you can redistribute it and/or modify it 5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as 6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this 7 * published by the Free Software Foundation. Oracle designates this
23 * questions. 23 * questions.
24 */ 24 */
25 25
26 package com.sun.xml.internal.ws.api.message.saaj; 26 package com.sun.xml.internal.ws.api.message.saaj;
27 27
28 import java.util.Iterator;
28 import java.util.Arrays; 29 import java.util.Arrays;
29 import java.util.Iterator; 30 import java.util.List;
31 import java.util.LinkedList;
30 32
31 import javax.xml.namespace.NamespaceContext; 33 import javax.xml.namespace.NamespaceContext;
32 import javax.xml.namespace.QName; 34 import javax.xml.namespace.QName;
33 import javax.xml.soap.SOAPElement; 35 import javax.xml.soap.SOAPElement;
34 import javax.xml.soap.SOAPException; 36 import javax.xml.soap.SOAPException;
40 import org.w3c.dom.Node; 42 import org.w3c.dom.Node;
41 43
42 /** 44 /**
43 * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface. 45 * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface.
44 * 46 *
47 * <p>
48 * Defers creation of SOAPElement until all the aspects of the name of the element are known.
49 * In some cases, the namespace uri is indicated only by the {@link #writeNamespace(String, String)} call.
50 * After opening an element ({@code writeStartElement}, {@code writeEmptyElement} methods), all attributes
51 * and namespace assignments are retained within {@link DeferredElement} object ({@code deferredElement} field).
52 * As soon as any other method than {@code writeAttribute}, {@code writeNamespace}, {@code writeDefaultNamespace}
53 * or {@code setNamespace} is called, the contents of {@code deferredElement} is transformed into new SOAPElement
54 * (which is appropriately inserted into the SOAPMessage under construction).
55 * This mechanism is necessary to fix JDK-8159058 issue.
56 * </p>
57 *
45 * @author shih-chang.chen@oracle.com 58 * @author shih-chang.chen@oracle.com
46 */ 59 */
47 public class SaajStaxWriter implements XMLStreamWriter { 60 public class SaajStaxWriter implements XMLStreamWriter {
48 61
49 protected SOAPMessage soap; 62 protected SOAPMessage soap;
50 protected String envURI; 63 protected String envURI;
51 protected SOAPElement currentElement; 64 protected SOAPElement currentElement;
65 protected DeferredElement deferredElement;
52 66
53 static final protected String Envelope = "Envelope"; 67 static final protected String Envelope = "Envelope";
54 static final protected String Header = "Header"; 68 static final protected String Header = "Header";
55 static final protected String Body = "Body"; 69 static final protected String Body = "Body";
56 static final protected String xmlns = "xmlns"; 70 static final protected String xmlns = "xmlns";
57 71
58 public SaajStaxWriter(final SOAPMessage msg) throws SOAPException { 72 public SaajStaxWriter(final SOAPMessage msg) throws SOAPException {
59 soap = msg; 73 soap = msg;
60 currentElement = soap.getSOAPPart().getEnvelope(); 74 currentElement = soap.getSOAPPart().getEnvelope();
61 envURI = currentElement.getNamespaceURI(); 75 envURI = currentElement.getNamespaceURI();
76 this.deferredElement = new DeferredElement();
62 } 77 }
63 78
64 public SOAPMessage getSOAPMessage() { 79 public SOAPMessage getSOAPMessage() {
65 return soap; 80 return soap;
66 } 81 }
67 82
68 @Override 83 @Override
69 public void writeStartElement(final String localName) throws XMLStreamException { 84 public void writeStartElement(final String localName) throws XMLStreamException {
70 try { 85 currentElement = deferredElement.flushTo(currentElement);
71 currentElement = currentElement.addChildElement(localName); 86 deferredElement.setLocalName(localName);
72 } catch (SOAPException e) {
73 throw new XMLStreamException(e);
74 }
75 } 87 }
76 88
77 @Override 89 @Override
78 public void writeStartElement(final String ns, final String ln) throws XMLStreamException { 90 public void writeStartElement(final String ns, final String ln) throws XMLStreamException {
79 writeStartElement(null, ln, ns); 91 writeStartElement(null, ln, ns);
80 } 92 }
81 93
82 @Override 94 @Override
83 public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException { 95 public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException {
84 try { 96 currentElement = deferredElement.flushTo(currentElement);
85 if (envURI.equals(ns)) { 97
98 if (envURI.equals(ns)) {
99 try {
86 if (Envelope.equals(ln)) { 100 if (Envelope.equals(ln)) {
87 currentElement = soap.getSOAPPart().getEnvelope(); 101 currentElement = soap.getSOAPPart().getEnvelope();
88 fixPrefix(prefix); 102 fixPrefix(prefix);
89 return; 103 return;
90 } else if (Header.equals(ln)) { 104 } else if (Header.equals(ln)) {
94 } else if (Body.equals(ln)) { 108 } else if (Body.equals(ln)) {
95 currentElement = soap.getSOAPBody(); 109 currentElement = soap.getSOAPBody();
96 fixPrefix(prefix); 110 fixPrefix(prefix);
97 return; 111 return;
98 } 112 }
99 } 113 } catch (SOAPException e) {
100 currentElement = (prefix == null) ? 114 throw new XMLStreamException(e);
101 currentElement.addChildElement(new QName(ns, ln)) : 115 }
102 currentElement.addChildElement(ln, prefix, ns); 116
103 } catch (SOAPException e) { 117 }
104 throw new XMLStreamException(e); 118
105 } 119 deferredElement.setLocalName(ln);
120 deferredElement.setNamespaceUri(ns);
121 deferredElement.setPrefix(prefix);
122
106 } 123 }
107 124
108 private void fixPrefix(final String prfx) throws XMLStreamException { 125 private void fixPrefix(final String prfx) throws XMLStreamException {
109 String oldPrfx = currentElement.getPrefix(); 126 String oldPrfx = currentElement.getPrefix();
110 if (prfx != null && !prfx.equals(oldPrfx)) { 127 if (prfx != null && !prfx.equals(oldPrfx)) {
127 writeStartElement(null, ln, null); 144 writeStartElement(null, ln, null);
128 } 145 }
129 146
130 @Override 147 @Override
131 public void writeEndElement() throws XMLStreamException { 148 public void writeEndElement() throws XMLStreamException {
149 currentElement = deferredElement.flushTo(currentElement);
132 if (currentElement != null) currentElement = currentElement.getParentElement(); 150 if (currentElement != null) currentElement = currentElement.getParentElement();
133 } 151 }
134 152
135 @Override 153 @Override
136 public void writeEndDocument() throws XMLStreamException { 154 public void writeEndDocument() throws XMLStreamException {
155 currentElement = deferredElement.flushTo(currentElement);
137 } 156 }
138 157
139 @Override 158 @Override
140 public void close() throws XMLStreamException { 159 public void close() throws XMLStreamException {
141 } 160 }
149 writeAttribute(null, null, ln, val); 168 writeAttribute(null, null, ln, val);
150 } 169 }
151 170
152 @Override 171 @Override
153 public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException { 172 public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException {
154 try { 173 if (ns == null && prefix == null && xmlns.equals(ln)) {
155 if (ns == null) { 174 writeNamespace("", value);
156 if (prefix == null && xmlns.equals(ln)) { 175 } else {
157 currentElement.addNamespaceDeclaration("", value); 176 if (deferredElement.isInitialized()) {
158 } else { 177 deferredElement.addAttribute(prefix, ns, ln, value);
159 currentElement.setAttributeNS("", ln, value);
160 }
161 } else { 178 } else {
162 QName name = (prefix == null) ? new QName(ns, ln) : new QName(ns, ln, prefix); 179 addAttibuteToElement(currentElement, prefix, ns, ln, value);
163 currentElement.addAttribute(name, value); 180 }
164 }
165 } catch (SOAPException e) {
166 throw new XMLStreamException(e);
167 } 181 }
168 } 182 }
169 183
170 @Override 184 @Override
171 public void writeAttribute(final String ns, final String ln, final String val) throws XMLStreamException { 185 public void writeAttribute(final String ns, final String ln, final String val) throws XMLStreamException {
172 writeAttribute(null, ns, ln, val); 186 writeAttribute(null, ns, ln, val);
173 } 187 }
174 188
175 @Override 189 @Override
176 public void writeNamespace(String prefix, final String uri) throws XMLStreamException { 190 public void writeNamespace(String prefix, final String uri) throws XMLStreamException {
177
178 // make prefix default if null or "xmlns" (according to javadoc) 191 // make prefix default if null or "xmlns" (according to javadoc)
179 if (prefix == null || "xmlns".equals(prefix)) { 192 String thePrefix = prefix == null || "xmlns".equals(prefix) ? "" : prefix;
180 prefix = ""; 193 if (deferredElement.isInitialized()) {
181 } 194 deferredElement.addNamespaceDeclaration(thePrefix, uri);
182 195 } else {
183 try { 196 try {
184 currentElement.addNamespaceDeclaration(prefix, uri); 197 currentElement.addNamespaceDeclaration(thePrefix, uri);
185 } catch (SOAPException e) { 198 } catch (SOAPException e) {
186 throw new XMLStreamException(e); 199 throw new XMLStreamException(e);
200 }
187 } 201 }
188 } 202 }
189 203
190 @Override 204 @Override
191 public void writeDefaultNamespace(final String uri) throws XMLStreamException { 205 public void writeDefaultNamespace(final String uri) throws XMLStreamException {
192 writeNamespace("", uri); 206 writeNamespace("", uri);
193 } 207 }
194 208
195 @Override 209 @Override
196 public void writeComment(final String data) throws XMLStreamException { 210 public void writeComment(final String data) throws XMLStreamException {
211 currentElement = deferredElement.flushTo(currentElement);
197 Comment c = soap.getSOAPPart().createComment(data); 212 Comment c = soap.getSOAPPart().createComment(data);
198 currentElement.appendChild(c); 213 currentElement.appendChild(c);
199 } 214 }
200 215
201 @Override 216 @Override
202 public void writeProcessingInstruction(final String target) throws XMLStreamException { 217 public void writeProcessingInstruction(final String target) throws XMLStreamException {
218 currentElement = deferredElement.flushTo(currentElement);
203 Node n = soap.getSOAPPart().createProcessingInstruction(target, ""); 219 Node n = soap.getSOAPPart().createProcessingInstruction(target, "");
204 currentElement.appendChild(n); 220 currentElement.appendChild(n);
205 } 221 }
206 222
207 @Override 223 @Override
208 public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException { 224 public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException {
225 currentElement = deferredElement.flushTo(currentElement);
209 Node n = soap.getSOAPPart().createProcessingInstruction(target, data); 226 Node n = soap.getSOAPPart().createProcessingInstruction(target, data);
210 currentElement.appendChild(n); 227 currentElement.appendChild(n);
211 } 228 }
212 229
213 @Override 230 @Override
214 public void writeCData(final String data) throws XMLStreamException { 231 public void writeCData(final String data) throws XMLStreamException {
232 currentElement = deferredElement.flushTo(currentElement);
215 Node n = soap.getSOAPPart().createCDATASection(data); 233 Node n = soap.getSOAPPart().createCDATASection(data);
216 currentElement.appendChild(n); 234 currentElement.appendChild(n);
217 } 235 }
218 236
219 @Override 237 @Override
220 public void writeDTD(final String dtd) throws XMLStreamException { 238 public void writeDTD(final String dtd) throws XMLStreamException {
221 //TODO ... Don't do anything here 239 currentElement = deferredElement.flushTo(currentElement);
222 } 240 }
223 241
224 @Override 242 @Override
225 public void writeEntityRef(final String name) throws XMLStreamException { 243 public void writeEntityRef(final String name) throws XMLStreamException {
244 currentElement = deferredElement.flushTo(currentElement);
226 Node n = soap.getSOAPPart().createEntityReference(name); 245 Node n = soap.getSOAPPart().createEntityReference(name);
227 currentElement.appendChild(n); 246 currentElement.appendChild(n);
228 } 247 }
229 248
230 @Override 249 @Override
248 } 267 }
249 } 268 }
250 269
251 @Override 270 @Override
252 public void writeCharacters(final String text) throws XMLStreamException { 271 public void writeCharacters(final String text) throws XMLStreamException {
272 currentElement = deferredElement.flushTo(currentElement);
253 try { 273 try {
254 currentElement.addTextNode(text); 274 currentElement.addTextNode(text);
255 } catch (SOAPException e) { 275 } catch (SOAPException e) {
256 throw new XMLStreamException(e); 276 throw new XMLStreamException(e);
257 } 277 }
258 } 278 }
259 279
260 @Override 280 @Override
261 public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException { 281 public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
282 currentElement = deferredElement.flushTo(currentElement);
262 char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len); 283 char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len);
263 try { 284 try {
264 currentElement.addTextNode(new String(chr)); 285 currentElement.addTextNode(new String(chr));
265 } catch (SOAPException e) { 286 } catch (SOAPException e) {
266 throw new XMLStreamException(e); 287 throw new XMLStreamException(e);
272 return currentElement.lookupPrefix(uri); 293 return currentElement.lookupPrefix(uri);
273 } 294 }
274 295
275 @Override 296 @Override
276 public void setPrefix(final String prefix, final String uri) throws XMLStreamException { 297 public void setPrefix(final String prefix, final String uri) throws XMLStreamException {
277 try { 298 // TODO: this in fact is not what would be expected from XMLStreamWriter
278 this.currentElement.addNamespaceDeclaration(prefix, uri); 299 // (e.g. XMLStreamWriter for writing to output stream does not write anything as result of
279 } catch (SOAPException e) { 300 // this method, it just rememebers that given prefix is associated with the given uri
280 throw new XMLStreamException(e); 301 // for the scope; to actually declare the prefix assignment in the resulting XML, one
302 // needs to call writeNamespace(...) method
303 // Kept for backwards compatibility reasons - this might be worth of further investigation.
304 if (deferredElement.isInitialized()) {
305 deferredElement.addNamespaceDeclaration(prefix, uri);
306 } else {
307 throw new XMLStreamException("Namespace not associated with any element");
281 } 308 }
282 } 309 }
283 310
284 @Override 311 @Override
285 public void setDefaultNamespace(final String uri) throws XMLStreamException { 312 public void setDefaultNamespace(final String uri) throws XMLStreamException {
306 } 333 }
307 public String getPrefix(final String namespaceURI) { 334 public String getPrefix(final String namespaceURI) {
308 return currentElement.lookupPrefix(namespaceURI); 335 return currentElement.lookupPrefix(namespaceURI);
309 } 336 }
310 public Iterator getPrefixes(final String namespaceURI) { 337 public Iterator getPrefixes(final String namespaceURI) {
311 return new Iterator() { 338 return new Iterator<String>() {
312 String prefix = getPrefix(namespaceURI); 339 String prefix = getPrefix(namespaceURI);
313 public boolean hasNext() { 340 public boolean hasNext() {
314 return (prefix != null); 341 return (prefix != null);
315 } 342 }
316 public Object next() { 343 public String next() {
317 if (!hasNext()) throw new java.util.NoSuchElementException(); 344 if (!hasNext()) throw new java.util.NoSuchElementException();
318 String next = prefix; 345 String next = prefix;
319 prefix = null; 346 prefix = null;
320 return next; 347 return next;
321 } 348 }
322 public void remove() {} 349 public void remove() {}
323 }; 350 };
324 } 351 }
325 }; 352 };
326 } 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 List<NamespaceDeclaration> namespaceDeclarations;
394 private final List<AttributeDeclaration> attributeDeclarations;
395
396 DeferredElement() {
397 this.namespaceDeclarations = new LinkedList<NamespaceDeclaration>();
398 this.attributeDeclarations = new LinkedList<AttributeDeclaration>();
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 && prefix.equals(emptyIfNull(this.prefix))) {
444 this.namespaceUri = namespaceUri;
445 }
446 this.namespaceDeclarations.add(new NamespaceDeclaration(prefix, namespaceUri));
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(prefix, value);
459 } else {
460 this.attributeDeclarations.add(new AttributeDeclaration(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 (NamespaceDeclaration namespace : this.namespaceDeclarations) {
495 target.addNamespaceDeclaration(namespace.prefix, namespace.namespaceUri);
496 }
497 // add attribute declarations
498 for (AttributeDeclaration attribute : this.attributeDeclarations) {
499 addAttibuteToElement(newElement,
500 attribute.prefix, attribute.namespaceUri, attribute.localName, attribute.value);
501 }
502 // reset state
503 this.reset();
504
505 return newElement;
506 } else {
507 return target;
508 }
509 // else after reset state -> not initialized
510 } catch (SOAPException e) {
511 throw new XMLStreamException(e);
512 }
513 }
514
515 /**
516 * Is the element initialized?
517 * @return boolean indicating whether it was initialized after last flush
518 */
519 public boolean isInitialized() {
520 return this.localName != null;
521 }
522
523 private void reset() {
524 this.localName = null;
525 this.prefix = null;
526 this.namespaceUri = null;
527 this.namespaceDeclarations.clear();
528 this.attributeDeclarations.clear();
529 }
530
531 private static String emptyIfNull(String s) {
532 return s == null ? "" : s;
533 }
534 }
535
536 static class NamespaceDeclaration {
537 final String prefix;
538 final String namespaceUri;
539
540 NamespaceDeclaration(String prefix, String namespaceUri) {
541 this.prefix = prefix;
542 this.namespaceUri = namespaceUri;
543 }
544 }
545
546 static class AttributeDeclaration {
547 final String prefix;
548 final String namespaceUri;
549 final String localName;
550 final String value;
551
552 AttributeDeclaration(String prefix, String namespaceUri, String localName, String value) {
553 this.prefix = prefix;
554 this.namespaceUri = namespaceUri;
555 this.localName = localName;
556 this.value = value;
557 }
558 }
327 } 559 }

mercurial