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

changeset 1377
1d5f442d50df
parent 1341
e5cc521294d8
child 1550
c4309a2d981b
     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  }

mercurial