Thu, 12 Jan 2017 00:25:07 +0300
8159058: SAXParseException when sending soap message
Reviewed-by: lancea, coffeys
1 /*
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.
4 *
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
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.xml.internal.ws.api.message.saaj;
28 import java.util.Iterator;
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.LinkedList;
33 import javax.xml.namespace.NamespaceContext;
34 import javax.xml.namespace.QName;
35 import javax.xml.soap.SOAPElement;
36 import javax.xml.soap.SOAPException;
37 import javax.xml.soap.SOAPMessage;
38 import javax.xml.stream.XMLStreamException;
39 import javax.xml.stream.XMLStreamWriter;
41 import org.w3c.dom.Comment;
42 import org.w3c.dom.Node;
44 /**
45 * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface.
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 *
58 * @author shih-chang.chen@oracle.com
59 */
60 public class SaajStaxWriter implements XMLStreamWriter {
62 protected SOAPMessage soap;
63 protected String envURI;
64 protected SOAPElement currentElement;
65 protected DeferredElement deferredElement;
67 static final protected String Envelope = "Envelope";
68 static final protected String Header = "Header";
69 static final protected String Body = "Body";
70 static final protected String xmlns = "xmlns";
72 public SaajStaxWriter(final SOAPMessage msg) throws SOAPException {
73 soap = msg;
74 currentElement = soap.getSOAPPart().getEnvelope();
75 envURI = currentElement.getNamespaceURI();
76 this.deferredElement = new DeferredElement();
77 }
79 public SOAPMessage getSOAPMessage() {
80 return soap;
81 }
83 @Override
84 public void writeStartElement(final String localName) throws XMLStreamException {
85 currentElement = deferredElement.flushTo(currentElement);
86 deferredElement.setLocalName(localName);
87 }
89 @Override
90 public void writeStartElement(final String ns, final String ln) throws XMLStreamException {
91 writeStartElement(null, ln, ns);
92 }
94 @Override
95 public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException {
96 currentElement = deferredElement.flushTo(currentElement);
98 if (envURI.equals(ns)) {
99 try {
100 if (Envelope.equals(ln)) {
101 currentElement = soap.getSOAPPart().getEnvelope();
102 fixPrefix(prefix);
103 return;
104 } else if (Header.equals(ln)) {
105 currentElement = soap.getSOAPHeader();
106 fixPrefix(prefix);
107 return;
108 } else if (Body.equals(ln)) {
109 currentElement = soap.getSOAPBody();
110 fixPrefix(prefix);
111 return;
112 }
113 } catch (SOAPException e) {
114 throw new XMLStreamException(e);
115 }
117 }
119 deferredElement.setLocalName(ln);
120 deferredElement.setNamespaceUri(ns);
121 deferredElement.setPrefix(prefix);
123 }
125 private void fixPrefix(final String prfx) throws XMLStreamException {
126 String oldPrfx = currentElement.getPrefix();
127 if (prfx != null && !prfx.equals(oldPrfx)) {
128 currentElement.setPrefix(prfx);
129 }
130 }
132 @Override
133 public void writeEmptyElement(final String uri, final String ln) throws XMLStreamException {
134 writeStartElement(null, ln, uri);
135 }
137 @Override
138 public void writeEmptyElement(final String prefix, final String ln, final String uri) throws XMLStreamException {
139 writeStartElement(prefix, ln, uri);
140 }
142 @Override
143 public void writeEmptyElement(final String ln) throws XMLStreamException {
144 writeStartElement(null, ln, null);
145 }
147 @Override
148 public void writeEndElement() throws XMLStreamException {
149 currentElement = deferredElement.flushTo(currentElement);
150 if (currentElement != null) currentElement = currentElement.getParentElement();
151 }
153 @Override
154 public void writeEndDocument() throws XMLStreamException {
155 currentElement = deferredElement.flushTo(currentElement);
156 }
158 @Override
159 public void close() throws XMLStreamException {
160 }
162 @Override
163 public void flush() throws XMLStreamException {
164 }
166 @Override
167 public void writeAttribute(final String ln, final String val) throws XMLStreamException {
168 writeAttribute(null, null, ln, val);
169 }
171 @Override
172 public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException {
173 if (ns == null && prefix == null && xmlns.equals(ln)) {
174 writeNamespace("", value);
175 } else {
176 if (deferredElement.isInitialized()) {
177 deferredElement.addAttribute(prefix, ns, ln, value);
178 } else {
179 addAttibuteToElement(currentElement, prefix, ns, ln, value);
180 }
181 }
182 }
184 @Override
185 public void writeAttribute(final String ns, final String ln, final String val) throws XMLStreamException {
186 writeAttribute(null, ns, ln, val);
187 }
189 @Override
190 public void writeNamespace(String prefix, final String uri) throws XMLStreamException {
191 // make prefix default if null or "xmlns" (according to javadoc)
192 String thePrefix = prefix == null || "xmlns".equals(prefix) ? "" : prefix;
193 if (deferredElement.isInitialized()) {
194 deferredElement.addNamespaceDeclaration(thePrefix, uri);
195 } else {
196 try {
197 currentElement.addNamespaceDeclaration(thePrefix, uri);
198 } catch (SOAPException e) {
199 throw new XMLStreamException(e);
200 }
201 }
202 }
204 @Override
205 public void writeDefaultNamespace(final String uri) throws XMLStreamException {
206 writeNamespace("", uri);
207 }
209 @Override
210 public void writeComment(final String data) throws XMLStreamException {
211 currentElement = deferredElement.flushTo(currentElement);
212 Comment c = soap.getSOAPPart().createComment(data);
213 currentElement.appendChild(c);
214 }
216 @Override
217 public void writeProcessingInstruction(final String target) throws XMLStreamException {
218 currentElement = deferredElement.flushTo(currentElement);
219 Node n = soap.getSOAPPart().createProcessingInstruction(target, "");
220 currentElement.appendChild(n);
221 }
223 @Override
224 public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException {
225 currentElement = deferredElement.flushTo(currentElement);
226 Node n = soap.getSOAPPart().createProcessingInstruction(target, data);
227 currentElement.appendChild(n);
228 }
230 @Override
231 public void writeCData(final String data) throws XMLStreamException {
232 currentElement = deferredElement.flushTo(currentElement);
233 Node n = soap.getSOAPPart().createCDATASection(data);
234 currentElement.appendChild(n);
235 }
237 @Override
238 public void writeDTD(final String dtd) throws XMLStreamException {
239 currentElement = deferredElement.flushTo(currentElement);
240 }
242 @Override
243 public void writeEntityRef(final String name) throws XMLStreamException {
244 currentElement = deferredElement.flushTo(currentElement);
245 Node n = soap.getSOAPPart().createEntityReference(name);
246 currentElement.appendChild(n);
247 }
249 @Override
250 public void writeStartDocument() throws XMLStreamException {
251 }
253 @Override
254 public void writeStartDocument(final String version) throws XMLStreamException {
255 if (version != null) soap.getSOAPPart().setXmlVersion(version);
256 }
258 @Override
259 public void writeStartDocument(final String encoding, final String version) throws XMLStreamException {
260 if (version != null) soap.getSOAPPart().setXmlVersion(version);
261 if (encoding != null) {
262 try {
263 soap.setProperty(SOAPMessage.CHARACTER_SET_ENCODING, encoding);
264 } catch (SOAPException e) {
265 throw new XMLStreamException(e);
266 }
267 }
268 }
270 @Override
271 public void writeCharacters(final String text) throws XMLStreamException {
272 currentElement = deferredElement.flushTo(currentElement);
273 try {
274 currentElement.addTextNode(text);
275 } catch (SOAPException e) {
276 throw new XMLStreamException(e);
277 }
278 }
280 @Override
281 public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException {
282 currentElement = deferredElement.flushTo(currentElement);
283 char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len);
284 try {
285 currentElement.addTextNode(new String(chr));
286 } catch (SOAPException e) {
287 throw new XMLStreamException(e);
288 }
289 }
291 @Override
292 public String getPrefix(final String uri) throws XMLStreamException {
293 return currentElement.lookupPrefix(uri);
294 }
296 @Override
297 public void setPrefix(final String prefix, final String uri) throws XMLStreamException {
298 // TODO: this in fact is not what would be expected from XMLStreamWriter
299 // (e.g. XMLStreamWriter for writing to output stream does not write anything as result of
300 // this method, it just rememebers that given prefix is associated with the given uri
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");
308 }
309 }
311 @Override
312 public void setDefaultNamespace(final String uri) throws XMLStreamException {
313 setPrefix("", uri);
314 }
316 @Override
317 public void setNamespaceContext(final NamespaceContext context)throws XMLStreamException {
318 throw new UnsupportedOperationException();
319 }
321 @Override
322 public Object getProperty(final String name) throws IllegalArgumentException {
323 //TODO the following line is to make eclipselink happy ... they are aware of this problem -
324 if (javax.xml.stream.XMLOutputFactory.IS_REPAIRING_NAMESPACES.equals(name)) return Boolean.FALSE;
325 return null;
326 }
328 @Override
329 public NamespaceContext getNamespaceContext() {
330 return new NamespaceContext() {
331 public String getNamespaceURI(final String prefix) {
332 return currentElement.getNamespaceURI(prefix);
333 }
334 public String getPrefix(final String namespaceURI) {
335 return currentElement.lookupPrefix(namespaceURI);
336 }
337 public Iterator getPrefixes(final String namespaceURI) {
338 return new Iterator<String>() {
339 String prefix = getPrefix(namespaceURI);
340 public boolean hasNext() {
341 return (prefix != null);
342 }
343 public String next() {
344 if (!hasNext()) throw new java.util.NoSuchElementException();
345 String next = prefix;
346 prefix = null;
347 return next;
348 }
349 public void remove() {}
350 };
351 }
352 };
353 }
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 }
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;
396 DeferredElement() {
397 this.namespaceDeclarations = new LinkedList<NamespaceDeclaration>();
398 this.attributeDeclarations = new LinkedList<AttributeDeclaration>();
399 reset();
400 }
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 }
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 }
427 /**
428 * Set namespace uri.
429 *
430 * @param namespaceUri namespace uri
431 */
432 public void setNamespaceUri(final String namespaceUri) {
433 this.namespaceUri = namespaceUri;
434 }
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 }
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 }
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();
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 }
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 }
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 }
531 private static String emptyIfNull(String s) {
532 return s == null ? "" : s;
533 }
534 }
536 static class NamespaceDeclaration {
537 final String prefix;
538 final String namespaceUri;
540 NamespaceDeclaration(String prefix, String namespaceUri) {
541 this.prefix = prefix;
542 this.namespaceUri = namespaceUri;
543 }
544 }
546 static class AttributeDeclaration {
547 final String prefix;
548 final String namespaceUri;
549 final String localName;
550 final String value;
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 }
559 }