From a60b599ac0fb611f3d51dc2a905195b65429b8cc Mon Sep 17 00:00:00 2001 From: aefimov Date: Thu, 12 Jan 2017 00:25:07 +0300 Subject: 8159058: SAXParseException when sending soap message Reviewed-by: lancea, coffeys --- .../messaging/saaj/soap/impl/DetailImpl.java | 30 +- .../messaging/saaj/soap/impl/ElementImpl.java | 33 +-- .../ws/api/message/saaj/SaajStaxWriter.java | 314 ++++++++++++++++++--- 3 files changed, 284 insertions(+), 93 deletions(-) diff --git a/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/DetailImpl.java b/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/DetailImpl.java index 034629e8..bd6727f0 100644 --- a/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/DetailImpl.java +++ b/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/DetailImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,13 +47,13 @@ public abstract class DetailImpl extends FaultElementImpl implements Detail { public DetailEntry addDetailEntry(Name name) throws SOAPException { DetailEntry entry = createDetailEntry(name); addNode(entry); - return (DetailEntry) circumventBug5034339(entry); + return entry; } public DetailEntry addDetailEntry(QName qname) throws SOAPException { DetailEntry entry = createDetailEntry(qname); addNode(entry); - return (DetailEntry) circumventBug5034339(entry); + return entry; } protected SOAPElement addElement(Name name) throws SOAPException { @@ -119,28 +119,4 @@ public abstract class DetailImpl extends FaultElementImpl implements Detail { return true; } - //overriding this method since the only two uses of this method - // are in ElementImpl and DetailImpl - //whereas the original base impl does the correct job for calls to it inside ElementImpl - // But it would not work for DetailImpl. - protected SOAPElement circumventBug5034339(SOAPElement element) { - - Name elementName = element.getElementName(); - if (!isNamespaceQualified(elementName)) { - String prefix = elementName.getPrefix(); - String defaultNamespace = getNamespaceURI(prefix); - if (defaultNamespace != null) { - Name newElementName = - NameImpl.create( - elementName.getLocalName(), - elementName.getPrefix(), - defaultNamespace); - SOAPElement newElement = createDetailEntry(newElementName); - replaceChild(newElement, element); - return newElement; - } - } - return element; - } - } diff --git a/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/ElementImpl.java b/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/ElementImpl.java index fc2dcfee..6b833370 100644 --- a/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/ElementImpl.java +++ b/src/share/jaxws_classes/com/sun/xml/internal/messaging/saaj/soap/impl/ElementImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -127,8 +127,11 @@ public class ElementImpl } public SOAPElement addChildElement(String localName) throws SOAPException { - return (SOAPElement) addChildElement( - NameImpl.createFromUnqualifiedName(localName)); + String nsUri = getNamespaceURI(""); + Name name = (nsUri == null || nsUri.isEmpty()) + ? NameImpl.createFromUnqualifiedName(localName) + : NameImpl.createFromQualifiedName(localName, nsUri); + return addChildElement(name); } public SOAPElement addChildElement(String localName, String prefix) @@ -372,13 +375,13 @@ public class ElementImpl protected SOAPElement addElement(Name name) throws SOAPException { SOAPElement newElement = createElement(name); addNode(newElement); - return circumventBug5034339(newElement); + return newElement; } protected SOAPElement addElement(QName name) throws SOAPException { SOAPElement newElement = createElement(name); addNode(newElement); - return circumventBug5034339(newElement); + return newElement; } protected SOAPElement createElement(Name name) { @@ -1201,26 +1204,6 @@ public class ElementImpl return !"".equals(name.getNamespaceURI()); } - protected SOAPElement circumventBug5034339(SOAPElement element) { - - Name elementName = element.getElementName(); - if (!isNamespaceQualified(elementName)) { - String prefix = elementName.getPrefix(); - String defaultNamespace = getNamespaceURI(prefix); - if (defaultNamespace != null) { - Name newElementName = - NameImpl.create( - elementName.getLocalName(), - elementName.getPrefix(), - defaultNamespace); - SOAPElement newElement = createElement(newElementName); - replaceChild(newElement, element); - return newElement; - } - } - return element; - } - //TODO: This is a temporary SAAJ workaround for optimizing XWS // should be removed once the corresponding JAXP bug is fixed // It appears the bug will be fixed in JAXP 1.4 (not by Appserver 9 timeframe) diff --git a/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java b/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java index 6414593f..f1ed6979 100644 --- a/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java +++ b/src/share/jaxws_classes/com/sun/xml/internal/ws/api/message/saaj/SaajStaxWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +25,10 @@ package com.sun.xml.internal.ws.api.message.saaj; -import java.util.Arrays; import java.util.Iterator; +import java.util.Arrays; +import java.util.List; +import java.util.LinkedList; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; @@ -42,6 +44,17 @@ import org.w3c.dom.Node; /** * SaajStaxWriter builds a SAAJ SOAPMessage by using XMLStreamWriter interface. * + *

+ * Defers creation of SOAPElement until all the aspects of the name of the element are known. + * In some cases, the namespace uri is indicated only by the {@link #writeNamespace(String, String)} call. + * After opening an element ({@code writeStartElement}, {@code writeEmptyElement} methods), all attributes + * and namespace assignments are retained within {@link DeferredElement} object ({@code deferredElement} field). + * As soon as any other method than {@code writeAttribute}, {@code writeNamespace}, {@code writeDefaultNamespace} + * or {@code setNamespace} is called, the contents of {@code deferredElement} is transformed into new SOAPElement + * (which is appropriately inserted into the SOAPMessage under construction). + * This mechanism is necessary to fix JDK-8159058 issue. + *

+ * * @author shih-chang.chen@oracle.com */ public class SaajStaxWriter implements XMLStreamWriter { @@ -49,6 +62,7 @@ public class SaajStaxWriter implements XMLStreamWriter { protected SOAPMessage soap; protected String envURI; protected SOAPElement currentElement; + protected DeferredElement deferredElement; static final protected String Envelope = "Envelope"; static final protected String Header = "Header"; @@ -59,6 +73,7 @@ public class SaajStaxWriter implements XMLStreamWriter { soap = msg; currentElement = soap.getSOAPPart().getEnvelope(); envURI = currentElement.getNamespaceURI(); + this.deferredElement = new DeferredElement(); } public SOAPMessage getSOAPMessage() { @@ -67,11 +82,8 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeStartElement(final String localName) throws XMLStreamException { - try { - currentElement = currentElement.addChildElement(localName); - } catch (SOAPException e) { - throw new XMLStreamException(e); - } + currentElement = deferredElement.flushTo(currentElement); + deferredElement.setLocalName(localName); } @Override @@ -81,8 +93,10 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeStartElement(final String prefix, final String ln, final String ns) throws XMLStreamException { - try { - if (envURI.equals(ns)) { + currentElement = deferredElement.flushTo(currentElement); + + if (envURI.equals(ns)) { + try { if (Envelope.equals(ln)) { currentElement = soap.getSOAPPart().getEnvelope(); fixPrefix(prefix); @@ -96,13 +110,16 @@ public class SaajStaxWriter implements XMLStreamWriter { fixPrefix(prefix); return; } + } catch (SOAPException e) { + throw new XMLStreamException(e); } - currentElement = (prefix == null) ? - currentElement.addChildElement(new QName(ns, ln)) : - currentElement.addChildElement(ln, prefix, ns); - } catch (SOAPException e) { - throw new XMLStreamException(e); + } + + deferredElement.setLocalName(ln); + deferredElement.setNamespaceUri(ns); + deferredElement.setPrefix(prefix); + } private void fixPrefix(final String prfx) throws XMLStreamException { @@ -129,11 +146,13 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeEndElement() throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); if (currentElement != null) currentElement = currentElement.getParentElement(); } @Override public void writeEndDocument() throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); } @Override @@ -151,19 +170,14 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeAttribute(final String prefix, final String ns, final String ln, final String value) throws XMLStreamException { - try { - if (ns == null) { - if (prefix == null && xmlns.equals(ln)) { - currentElement.addNamespaceDeclaration("", value); - } else { - currentElement.setAttributeNS("", ln, value); - } + if (ns == null && prefix == null && xmlns.equals(ln)) { + writeNamespace("", value); + } else { + if (deferredElement.isInitialized()) { + deferredElement.addAttribute(prefix, ns, ln, value); } else { - QName name = (prefix == null) ? new QName(ns, ln) : new QName(ns, ln, prefix); - currentElement.addAttribute(name, value); + addAttibuteToElement(currentElement, prefix, ns, ln, value); } - } catch (SOAPException e) { - throw new XMLStreamException(e); } } @@ -174,16 +188,16 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeNamespace(String prefix, final String uri) throws XMLStreamException { - // make prefix default if null or "xmlns" (according to javadoc) - if (prefix == null || "xmlns".equals(prefix)) { - prefix = ""; - } - - try { - currentElement.addNamespaceDeclaration(prefix, uri); - } catch (SOAPException e) { - throw new XMLStreamException(e); + String thePrefix = prefix == null || "xmlns".equals(prefix) ? "" : prefix; + if (deferredElement.isInitialized()) { + deferredElement.addNamespaceDeclaration(thePrefix, uri); + } else { + try { + currentElement.addNamespaceDeclaration(thePrefix, uri); + } catch (SOAPException e) { + throw new XMLStreamException(e); + } } } @@ -194,35 +208,40 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeComment(final String data) throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); Comment c = soap.getSOAPPart().createComment(data); currentElement.appendChild(c); } @Override public void writeProcessingInstruction(final String target) throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); Node n = soap.getSOAPPart().createProcessingInstruction(target, ""); currentElement.appendChild(n); } @Override public void writeProcessingInstruction(final String target, final String data) throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); Node n = soap.getSOAPPart().createProcessingInstruction(target, data); currentElement.appendChild(n); } @Override public void writeCData(final String data) throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); Node n = soap.getSOAPPart().createCDATASection(data); currentElement.appendChild(n); } @Override public void writeDTD(final String dtd) throws XMLStreamException { - //TODO ... Don't do anything here + currentElement = deferredElement.flushTo(currentElement); } @Override public void writeEntityRef(final String name) throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); Node n = soap.getSOAPPart().createEntityReference(name); currentElement.appendChild(n); } @@ -250,6 +269,7 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeCharacters(final String text) throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); try { currentElement.addTextNode(text); } catch (SOAPException e) { @@ -259,6 +279,7 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void writeCharacters(final char[] text, final int start, final int len) throws XMLStreamException { + currentElement = deferredElement.flushTo(currentElement); char[] chr = (start == 0 && len == text.length) ? text : Arrays.copyOfRange(text, start, start + len); try { currentElement.addTextNode(new String(chr)); @@ -274,10 +295,16 @@ public class SaajStaxWriter implements XMLStreamWriter { @Override public void setPrefix(final String prefix, final String uri) throws XMLStreamException { - try { - this.currentElement.addNamespaceDeclaration(prefix, uri); - } catch (SOAPException e) { - throw new XMLStreamException(e); + // TODO: this in fact is not what would be expected from XMLStreamWriter + // (e.g. XMLStreamWriter for writing to output stream does not write anything as result of + // this method, it just rememebers that given prefix is associated with the given uri + // for the scope; to actually declare the prefix assignment in the resulting XML, one + // needs to call writeNamespace(...) method + // Kept for backwards compatibility reasons - this might be worth of further investigation. + if (deferredElement.isInitialized()) { + deferredElement.addNamespaceDeclaration(prefix, uri); + } else { + throw new XMLStreamException("Namespace not associated with any element"); } } @@ -308,12 +335,12 @@ public class SaajStaxWriter implements XMLStreamWriter { return currentElement.lookupPrefix(namespaceURI); } public Iterator getPrefixes(final String namespaceURI) { - return new Iterator() { + return new Iterator() { String prefix = getPrefix(namespaceURI); public boolean hasNext() { return (prefix != null); } - public Object next() { + public String next() { if (!hasNext()) throw new java.util.NoSuchElementException(); String next = prefix; prefix = null; @@ -324,4 +351,209 @@ public class SaajStaxWriter implements XMLStreamWriter { } }; } + + static void addAttibuteToElement(SOAPElement element, String prefix, String ns, String ln, String value) + throws XMLStreamException { + try { + if (ns == null) { + element.setAttributeNS("", ln, value); + } else { + QName name = prefix == null ? new QName(ns, ln) : new QName(ns, ln, prefix); + element.addAttribute(name, value); + } + } catch (SOAPException e) { + throw new XMLStreamException(e); + } + } + + /** + * Holds details of element that needs to be deferred in order to manage namespace assignments correctly. + * + *

+ * An instance of can be set with all the aspects of the element name (local name, prefix, namespace uri). + * Attributes and namespace declarations (special case of attribute) can be added. + * Namespace declarations are handled so that the element namespace is updated if it is implied by the namespace + * declaration and the namespace was not set to non-{@code null} value previously. + *

+ * + *

+ * The state of this object can be {@link #flushTo(SOAPElement) flushed} to SOAPElement - new SOAPElement will + * be added a child element; the new element will have exactly the shape as represented by the state of this + * object. Note that the {@link #flushTo(SOAPElement)} method does nothing + * (and returns the argument immediately) if the state of this object is not initialized + * (i.e. local name is null). + *

+ * + * @author ondrej.cerny@oracle.com + */ + static class DeferredElement { + private String prefix; + private String localName; + private String namespaceUri; + private final List namespaceDeclarations; + private final List attributeDeclarations; + + DeferredElement() { + this.namespaceDeclarations = new LinkedList(); + this.attributeDeclarations = new LinkedList(); + reset(); + } + + + /** + * Set prefix of the element. + * @param prefix namespace prefix + */ + public void setPrefix(final String prefix) { + this.prefix = prefix; + } + + /** + * Set local name of the element. + * + *

+ * This method initializes the element. + *

+ * + * @param localName local name {@code not null} + */ + public void setLocalName(final String localName) { + if (localName == null) { + throw new IllegalArgumentException("localName can not be null"); + } + this.localName = localName; + } + + /** + * Set namespace uri. + * + * @param namespaceUri namespace uri + */ + public void setNamespaceUri(final String namespaceUri) { + this.namespaceUri = namespaceUri; + } + + /** + * Adds namespace prefix assignment to the element. + * + * @param prefix prefix (not {@code null}) + * @param namespaceUri namespace uri + */ + public void addNamespaceDeclaration(final String prefix, final String namespaceUri) { + if (null == this.namespaceUri && null != namespaceUri && prefix.equals(emptyIfNull(this.prefix))) { + this.namespaceUri = namespaceUri; + } + this.namespaceDeclarations.add(new NamespaceDeclaration(prefix, namespaceUri)); + } + + /** + * Adds attribute to the element. + * @param prefix prefix + * @param ns namespace + * @param ln local name + * @param value value + */ + public void addAttribute(final String prefix, final String ns, final String ln, final String value) { + if (ns == null && prefix == null && xmlns.equals(ln)) { + this.addNamespaceDeclaration(prefix, value); + } else { + this.attributeDeclarations.add(new AttributeDeclaration(prefix, ns, ln, value)); + } + } + + /** + * Flushes state of this element to the {@code target} element. + * + *

+ * If this element is initialized then it is added with all the namespace declarations and attributes + * to the {@code target} element as a child. The state of this element is reset to uninitialized. + * The newly added element object is returned. + *

+ *

+ * If this element is not initialized then the {@code target} is returned immediately, nothing else is done. + *

+ * + * @param target target element + * @return {@code target} or new element + * @throws XMLStreamException on error + */ + public SOAPElement flushTo(final SOAPElement target) throws XMLStreamException { + try { + if (this.localName != null) { + // add the element appropriately (based on namespace declaration) + final SOAPElement newElement; + if (this.namespaceUri == null) { + // add element with inherited scope + newElement = target.addChildElement(this.localName); + } else if (prefix == null) { + newElement = target.addChildElement(new QName(this.namespaceUri, this.localName)); + } else { + newElement = target.addChildElement(this.localName, this.prefix, this.namespaceUri); + } + // add namespace declarations + for (NamespaceDeclaration namespace : this.namespaceDeclarations) { + target.addNamespaceDeclaration(namespace.prefix, namespace.namespaceUri); + } + // add attribute declarations + for (AttributeDeclaration attribute : this.attributeDeclarations) { + addAttibuteToElement(newElement, + attribute.prefix, attribute.namespaceUri, attribute.localName, attribute.value); + } + // reset state + this.reset(); + + return newElement; + } else { + return target; + } + // else after reset state -> not initialized + } catch (SOAPException e) { + throw new XMLStreamException(e); + } + } + + /** + * Is the element initialized? + * @return boolean indicating whether it was initialized after last flush + */ + public boolean isInitialized() { + return this.localName != null; + } + + private void reset() { + this.localName = null; + this.prefix = null; + this.namespaceUri = null; + this.namespaceDeclarations.clear(); + this.attributeDeclarations.clear(); + } + + private static String emptyIfNull(String s) { + return s == null ? "" : s; + } + } + + static class NamespaceDeclaration { + final String prefix; + final String namespaceUri; + + NamespaceDeclaration(String prefix, String namespaceUri) { + this.prefix = prefix; + this.namespaceUri = namespaceUri; + } + } + + static class AttributeDeclaration { + final String prefix; + final String namespaceUri; + final String localName; + final String value; + + AttributeDeclaration(String prefix, String namespaceUri, String localName, String value) { + this.prefix = prefix; + this.namespaceUri = namespaceUri; + this.localName = localName; + this.value = value; + } + } } -- cgit v1.2.3