1.1 --- a/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java Mon May 01 10:54:49 2017 -0700 1.2 +++ b/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java Thu Jan 12 00:25:07 2017 +0300 1.3 @@ -1,5 +1,5 @@ 1.4 /* 1.5 - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 1.6 + * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. 1.7 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 1.8 * 1.9 * This code is free software; you can redistribute it and/or modify it 1.10 @@ -25,8 +25,10 @@ 1.11 1.12 package com.sun.xml.internal.ws.api.message.saaj; 1.13 1.14 +import java.util.Iterator; 1.15 import java.util.Arrays; 1.16 -import java.util.Iterator; 1.17 +import java.util.List; 1.18 +import java.util.LinkedList; 1.19 1.20 import javax.xml.namespace.NamespaceContext; 1.21 import javax.xml.namespace.QName; 1.22 @@ -42,6 +44,17 @@ 1.23 /** 1.24 * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface. 1.25 * 1.26 + * <p> 1.27 + * Defers creation of SOAPElement until all the aspects of the name of the element are known. 1.28 + * In some cases, the namespace uri is indicated only by the {@link #writeNamespace(String, String)} call. 1.29 + * After opening an element ({@code writeStartElement}, {@code writeEmptyElement} methods), all attributes 1.30 + * and namespace assignments are retained within {@link DeferredElement} object ({@code deferredElement} field). 1.31 + * As soon as any other method than {@code writeAttribute}, {@code writeNamespace}, {@code writeDefaultNamespace} 1.32 + * or {@code setNamespace} is called, the contents of {@code deferredElement} is transformed into new SOAPElement 1.33 + * (which is appropriately inserted into the SOAPMessage under construction). 1.34 + * This mechanism is necessary to fix JDK-8159058 issue. 1.35 + * </p> 1.36 + * 1.37 * @author shih-chang.chen@oracle.com 1.38 */ 1.39 public class SaajStaxWriter implements XMLStreamWriter { 1.40 @@ -49,6 +62,7 @@ 1.41 protected SOAPMessage soap; 1.42 protected String envURI; 1.43 protected SOAPElement currentElement; 1.44 + protected DeferredElement deferredElement; 1.45 1.46 static final protected String Envelope = "Envelope"; 1.47 static final protected String Header = "Header"; 1.48 @@ -59,6 +73,7 @@ 1.49 soap = msg; 1.50 currentElement = soap.getSOAPPart().getEnvelope(); 1.51 envURI = currentElement.getNamespaceURI(); 1.52 + this.deferredElement = new DeferredElement(); 1.53 } 1.54 1.55 public SOAPMessage getSOAPMessage() { 1.56 @@ -67,11 +82,8 @@ 1.57 1.58 @Override 1.59 public void writeStartElement(final String localName) throws XMLStreamException { 1.60 - try { 1.61 - currentElement = currentElement.addChildElement(localName); 1.62 - } catch (SOAPException e) { 1.63 - throw new XMLStreamException(e); 1.64 - } 1.65 + currentElement = deferredElement.flushTo(currentElement); 1.66 + deferredElement.setLocalName(localName); 1.67 } 1.68 1.69 @Override 1.70 @@ -81,8 +93,10 @@ 1.71 1.72 @Override 1.73 public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException { 1.74 - try { 1.75 - if (envURI.equals(ns)) { 1.76 + currentElement = deferredElement.flushTo(currentElement); 1.77 + 1.78 + if (envURI.equals(ns)) { 1.79 + try { 1.80 if (Envelope.equals(ln)) { 1.81 currentElement = soap.getSOAPPart().getEnvelope(); 1.82 fixPrefix(prefix); 1.83 @@ -96,13 +110,16 @@ 1.84 fixPrefix(prefix); 1.85 return; 1.86 } 1.87 + } catch (SOAPException e) { 1.88 + throw new XMLStreamException(e); 1.89 } 1.90 - currentElement = (prefix == null) ? 1.91 - currentElement.addChildElement(new QName(ns, ln)) : 1.92 - currentElement.addChildElement(ln, prefix, ns); 1.93 - } catch (SOAPException e) { 1.94 - throw new XMLStreamException(e); 1.95 + 1.96 } 1.97 + 1.98 + deferredElement.setLocalName(ln); 1.99 + deferredElement.setNamespaceUri(ns); 1.100 + deferredElement.setPrefix(prefix); 1.101 + 1.102 } 1.103 1.104 private void fixPrefix(final String prfx) throws XMLStreamException { 1.105 @@ -129,11 +146,13 @@ 1.106 1.107 @Override 1.108 public void writeEndElement() throws XMLStreamException { 1.109 + currentElement = deferredElement.flushTo(currentElement); 1.110 if (currentElement != null) currentElement = currentElement.getParentElement(); 1.111 } 1.112 1.113 @Override 1.114 public void writeEndDocument() throws XMLStreamException { 1.115 + currentElement = deferredElement.flushTo(currentElement); 1.116 } 1.117 1.118 @Override 1.119 @@ -151,19 +170,14 @@ 1.120 1.121 @Override 1.122 public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException { 1.123 - try { 1.124 - if (ns == null) { 1.125 - if (prefix == null && xmlns.equals(ln)) { 1.126 - currentElement.addNamespaceDeclaration("", value); 1.127 - } else { 1.128 - currentElement.setAttributeNS("", ln, value); 1.129 - } 1.130 + if (ns == null && prefix == null && xmlns.equals(ln)) { 1.131 + writeNamespace("", value); 1.132 + } else { 1.133 + if (deferredElement.isInitialized()) { 1.134 + deferredElement.addAttribute(prefix, ns, ln, value); 1.135 } else { 1.136 - QName name = (prefix == null) ? new QName(ns, ln) : new QName(ns, ln, prefix); 1.137 - currentElement.addAttribute(name, value); 1.138 + addAttibuteToElement(currentElement, prefix, ns, ln, value); 1.139 } 1.140 - } catch (SOAPException e) { 1.141 - throw new XMLStreamException(e); 1.142 } 1.143 } 1.144 1.145 @@ -174,16 +188,16 @@ 1.146 1.147 @Override 1.148 public void writeNamespace(String prefix, final String uri) throws XMLStreamException { 1.149 - 1.150 // make prefix default if null or "xmlns" (according to javadoc) 1.151 - if (prefix == null || "xmlns".equals(prefix)) { 1.152 - prefix = ""; 1.153 - } 1.154 - 1.155 - try { 1.156 - currentElement.addNamespaceDeclaration(prefix, uri); 1.157 - } catch (SOAPException e) { 1.158 - throw new XMLStreamException(e); 1.159 + String thePrefix = prefix == null || "xmlns".equals(prefix) ? "" : prefix; 1.160 + if (deferredElement.isInitialized()) { 1.161 + deferredElement.addNamespaceDeclaration(thePrefix, uri); 1.162 + } else { 1.163 + try { 1.164 + currentElement.addNamespaceDeclaration(thePrefix, uri); 1.165 + } catch (SOAPException e) { 1.166 + throw new XMLStreamException(e); 1.167 + } 1.168 } 1.169 } 1.170 1.171 @@ -194,35 +208,40 @@ 1.172 1.173 @Override 1.174 public void writeComment(final String data) throws XMLStreamException { 1.175 + currentElement = deferredElement.flushTo(currentElement); 1.176 Comment c = soap.getSOAPPart().createComment(data); 1.177 currentElement.appendChild(c); 1.178 } 1.179 1.180 @Override 1.181 public void writeProcessingInstruction(final String target) throws XMLStreamException { 1.182 + currentElement = deferredElement.flushTo(currentElement); 1.183 Node n = soap.getSOAPPart().createProcessingInstruction(target, ""); 1.184 currentElement.appendChild(n); 1.185 } 1.186 1.187 @Override 1.188 public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException { 1.189 + currentElement = deferredElement.flushTo(currentElement); 1.190 Node n = soap.getSOAPPart().createProcessingInstruction(target, data); 1.191 currentElement.appendChild(n); 1.192 } 1.193 1.194 @Override 1.195 public void writeCData(final String data) throws XMLStreamException { 1.196 + currentElement = deferredElement.flushTo(currentElement); 1.197 Node n = soap.getSOAPPart().createCDATASection(data); 1.198 currentElement.appendChild(n); 1.199 } 1.200 1.201 @Override 1.202 public void writeDTD(final String dtd) throws XMLStreamException { 1.203 - //TODO ... Don't do anything here 1.204 + currentElement = deferredElement.flushTo(currentElement); 1.205 } 1.206 1.207 @Override 1.208 public void writeEntityRef(final String name) throws XMLStreamException { 1.209 + currentElement = deferredElement.flushTo(currentElement); 1.210 Node n = soap.getSOAPPart().createEntityReference(name); 1.211 currentElement.appendChild(n); 1.212 } 1.213 @@ -250,6 +269,7 @@ 1.214 1.215 @Override 1.216 public void writeCharacters(final String text) throws XMLStreamException { 1.217 + currentElement = deferredElement.flushTo(currentElement); 1.218 try { 1.219 currentElement.addTextNode(text); 1.220 } catch (SOAPException e) { 1.221 @@ -259,6 +279,7 @@ 1.222 1.223 @Override 1.224 public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException { 1.225 + currentElement = deferredElement.flushTo(currentElement); 1.226 char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len); 1.227 try { 1.228 currentElement.addTextNode(new String(chr)); 1.229 @@ -274,10 +295,16 @@ 1.230 1.231 @Override 1.232 public void setPrefix(final String prefix, final String uri) throws XMLStreamException { 1.233 - try { 1.234 - this.currentElement.addNamespaceDeclaration(prefix, uri); 1.235 - } catch (SOAPException e) { 1.236 - throw new XMLStreamException(e); 1.237 + // TODO: this in fact is not what would be expected from XMLStreamWriter 1.238 + // (e.g. XMLStreamWriter for writing to output stream does not write anything as result of 1.239 + // this method, it just rememebers that given prefix is associated with the given uri 1.240 + // for the scope; to actually declare the prefix assignment in the resulting XML, one 1.241 + // needs to call writeNamespace(...) method 1.242 + // Kept for backwards compatibility reasons - this might be worth of further investigation. 1.243 + if (deferredElement.isInitialized()) { 1.244 + deferredElement.addNamespaceDeclaration(prefix, uri); 1.245 + } else { 1.246 + throw new XMLStreamException("Namespace not associated with any element"); 1.247 } 1.248 } 1.249 1.250 @@ -308,12 +335,12 @@ 1.251 return currentElement.lookupPrefix(namespaceURI); 1.252 } 1.253 public Iterator getPrefixes(final String namespaceURI) { 1.254 - return new Iterator() { 1.255 + return new Iterator<String>() { 1.256 String prefix = getPrefix(namespaceURI); 1.257 public boolean hasNext() { 1.258 return (prefix != null); 1.259 } 1.260 - public Object next() { 1.261 + public String next() { 1.262 if (!hasNext()) throw new java.util.NoSuchElementException(); 1.263 String next = prefix; 1.264 prefix = null; 1.265 @@ -324,4 +351,209 @@ 1.266 } 1.267 }; 1.268 } 1.269 + 1.270 + static void addAttibuteToElement(SOAPElement element, String prefix, String ns, String ln, String value) 1.271 + throws XMLStreamException { 1.272 + try { 1.273 + if (ns == null) { 1.274 + element.setAttributeNS("", ln, value); 1.275 + } else { 1.276 + QName name = prefix == null ? new QName(ns, ln) : new QName(ns, ln, prefix); 1.277 + element.addAttribute(name, value); 1.278 + } 1.279 + } catch (SOAPException e) { 1.280 + throw new XMLStreamException(e); 1.281 + } 1.282 + } 1.283 + 1.284 + /** 1.285 + * Holds details of element that needs to be deferred in order to manage namespace assignments correctly. 1.286 + * 1.287 + * <p> 1.288 + * An instance of can be set with all the aspects of the element name (local name, prefix, namespace uri). 1.289 + * Attributes and namespace declarations (special case of attribute) can be added. 1.290 + * Namespace declarations are handled so that the element namespace is updated if it is implied by the namespace 1.291 + * declaration and the namespace was not set to non-{@code null} value previously. 1.292 + * </p> 1.293 + * 1.294 + * <p> 1.295 + * The state of this object can be {@link #flushTo(SOAPElement) flushed} to SOAPElement - new SOAPElement will 1.296 + * be added a child element; the new element will have exactly the shape as represented by the state of this 1.297 + * object. Note that the {@link #flushTo(SOAPElement)} method does nothing 1.298 + * (and returns the argument immediately) if the state of this object is not initialized 1.299 + * (i.e. local name is null). 1.300 + * </p> 1.301 + * 1.302 + * @author ondrej.cerny@oracle.com 1.303 + */ 1.304 + static class DeferredElement { 1.305 + private String prefix; 1.306 + private String localName; 1.307 + private String namespaceUri; 1.308 + private final List<NamespaceDeclaration> namespaceDeclarations; 1.309 + private final List<AttributeDeclaration> attributeDeclarations; 1.310 + 1.311 + DeferredElement() { 1.312 + this.namespaceDeclarations = new LinkedList<NamespaceDeclaration>(); 1.313 + this.attributeDeclarations = new LinkedList<AttributeDeclaration>(); 1.314 + reset(); 1.315 + } 1.316 + 1.317 + 1.318 + /** 1.319 + * Set prefix of the element. 1.320 + * @param prefix namespace prefix 1.321 + */ 1.322 + public void setPrefix(final String prefix) { 1.323 + this.prefix = prefix; 1.324 + } 1.325 + 1.326 + /** 1.327 + * Set local name of the element. 1.328 + * 1.329 + * <p> 1.330 + * This method initializes the element. 1.331 + * </p> 1.332 + * 1.333 + * @param localName local name {@code not null} 1.334 + */ 1.335 + public void setLocalName(final String localName) { 1.336 + if (localName == null) { 1.337 + throw new IllegalArgumentException("localName can not be null"); 1.338 + } 1.339 + this.localName = localName; 1.340 + } 1.341 + 1.342 + /** 1.343 + * Set namespace uri. 1.344 + * 1.345 + * @param namespaceUri namespace uri 1.346 + */ 1.347 + public void setNamespaceUri(final String namespaceUri) { 1.348 + this.namespaceUri = namespaceUri; 1.349 + } 1.350 + 1.351 + /** 1.352 + * Adds namespace prefix assignment to the element. 1.353 + * 1.354 + * @param prefix prefix (not {@code null}) 1.355 + * @param namespaceUri namespace uri 1.356 + */ 1.357 + public void addNamespaceDeclaration(final String prefix, final String namespaceUri) { 1.358 + if (null == this.namespaceUri && null != namespaceUri && prefix.equals(emptyIfNull(this.prefix))) { 1.359 + this.namespaceUri = namespaceUri; 1.360 + } 1.361 + this.namespaceDeclarations.add(new NamespaceDeclaration(prefix, namespaceUri)); 1.362 + } 1.363 + 1.364 + /** 1.365 + * Adds attribute to the element. 1.366 + * @param prefix prefix 1.367 + * @param ns namespace 1.368 + * @param ln local name 1.369 + * @param value value 1.370 + */ 1.371 + public void addAttribute(final String prefix, final String ns, final String ln, final String value) { 1.372 + if (ns == null && prefix == null && xmlns.equals(ln)) { 1.373 + this.addNamespaceDeclaration(prefix, value); 1.374 + } else { 1.375 + this.attributeDeclarations.add(new AttributeDeclaration(prefix, ns, ln, value)); 1.376 + } 1.377 + } 1.378 + 1.379 + /** 1.380 + * Flushes state of this element to the {@code target} element. 1.381 + * 1.382 + * <p> 1.383 + * If this element is initialized then it is added with all the namespace declarations and attributes 1.384 + * to the {@code target} element as a child. The state of this element is reset to uninitialized. 1.385 + * The newly added element object is returned. 1.386 + * </p> 1.387 + * <p> 1.388 + * If this element is not initialized then the {@code target} is returned immediately, nothing else is done. 1.389 + * </p> 1.390 + * 1.391 + * @param target target element 1.392 + * @return {@code target} or new element 1.393 + * @throws XMLStreamException on error 1.394 + */ 1.395 + public SOAPElement flushTo(final SOAPElement target) throws XMLStreamException { 1.396 + try { 1.397 + if (this.localName != null) { 1.398 + // add the element appropriately (based on namespace declaration) 1.399 + final SOAPElement newElement; 1.400 + if (this.namespaceUri == null) { 1.401 + // add element with inherited scope 1.402 + newElement = target.addChildElement(this.localName); 1.403 + } else if (prefix == null) { 1.404 + newElement = target.addChildElement(new QName(this.namespaceUri, this.localName)); 1.405 + } else { 1.406 + newElement = target.addChildElement(this.localName, this.prefix, this.namespaceUri); 1.407 + } 1.408 + // add namespace declarations 1.409 + for (NamespaceDeclaration namespace : this.namespaceDeclarations) { 1.410 + target.addNamespaceDeclaration(namespace.prefix, namespace.namespaceUri); 1.411 + } 1.412 + // add attribute declarations 1.413 + for (AttributeDeclaration attribute : this.attributeDeclarations) { 1.414 + addAttibuteToElement(newElement, 1.415 + attribute.prefix, attribute.namespaceUri, attribute.localName, attribute.value); 1.416 + } 1.417 + // reset state 1.418 + this.reset(); 1.419 + 1.420 + return newElement; 1.421 + } else { 1.422 + return target; 1.423 + } 1.424 + // else after reset state -> not initialized 1.425 + } catch (SOAPException e) { 1.426 + throw new XMLStreamException(e); 1.427 + } 1.428 + } 1.429 + 1.430 + /** 1.431 + * Is the element initialized? 1.432 + * @return boolean indicating whether it was initialized after last flush 1.433 + */ 1.434 + public boolean isInitialized() { 1.435 + return this.localName != null; 1.436 + } 1.437 + 1.438 + private void reset() { 1.439 + this.localName = null; 1.440 + this.prefix = null; 1.441 + this.namespaceUri = null; 1.442 + this.namespaceDeclarations.clear(); 1.443 + this.attributeDeclarations.clear(); 1.444 + } 1.445 + 1.446 + private static String emptyIfNull(String s) { 1.447 + return s == null ? "" : s; 1.448 + } 1.449 + } 1.450 + 1.451 + static class NamespaceDeclaration { 1.452 + final String prefix; 1.453 + final String namespaceUri; 1.454 + 1.455 + NamespaceDeclaration(String prefix, String namespaceUri) { 1.456 + this.prefix = prefix; 1.457 + this.namespaceUri = namespaceUri; 1.458 + } 1.459 + } 1.460 + 1.461 + static class AttributeDeclaration { 1.462 + final String prefix; 1.463 + final String namespaceUri; 1.464 + final String localName; 1.465 + final String value; 1.466 + 1.467 + AttributeDeclaration(String prefix, String namespaceUri, String localName, String value) { 1.468 + this.prefix = prefix; 1.469 + this.namespaceUri = namespaceUri; 1.470 + this.localName = localName; 1.471 + this.value = value; 1.472 + } 1.473 + } 1.474 }