1 /*
   2  * Copyright (c) 1997, 2014, 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  */
  25 
  26 package com.sun.xml.internal.ws.message.saaj;
  27 
  28 import com.sun.istack.internal.FragmentContentHandler;
  29 import com.sun.istack.internal.NotNull;
  30 import com.sun.istack.internal.Nullable;
  31 import com.sun.istack.internal.XMLStreamException2;
  32 import com.sun.xml.internal.bind.api.Bridge;
  33 import com.sun.xml.internal.bind.unmarshaller.DOMScanner;
  34 import com.sun.xml.internal.ws.api.SOAPVersion;
  35 import com.sun.xml.internal.ws.api.message.*;
  36 import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
  37 import com.sun.xml.internal.ws.spi.db.XMLBridge;
  38 import com.sun.xml.internal.ws.streaming.DOMStreamReader;
  39 import com.sun.xml.internal.ws.util.ASCIIUtility;
  40 import com.sun.xml.internal.ws.util.DOMUtil;
  41 import org.w3c.dom.Attr;
  42 import org.w3c.dom.Element;
  43 import org.w3c.dom.NamedNodeMap;
  44 import org.w3c.dom.Node;
  45 import org.xml.sax.ContentHandler;
  46 import org.xml.sax.ErrorHandler;
  47 import org.xml.sax.SAXException;
  48 import org.xml.sax.helpers.AttributesImpl;
  49 import org.xml.sax.helpers.LocatorImpl;
  50 
  51 import javax.activation.DataHandler;
  52 import javax.xml.bind.JAXBException;
  53 import javax.xml.bind.Unmarshaller;
  54 import javax.xml.soap.*;
  55 import javax.xml.stream.XMLStreamException;
  56 import javax.xml.stream.XMLStreamReader;
  57 import javax.xml.stream.XMLStreamWriter;
  58 import javax.xml.transform.Source;
  59 import javax.xml.transform.dom.DOMSource;
  60 import javax.xml.transform.stream.StreamSource;
  61 import javax.xml.ws.WebServiceException;
  62 import java.io.IOException;
  63 import java.io.InputStream;
  64 import java.io.OutputStream;
  65 import java.util.HashMap;
  66 import java.util.Iterator;
  67 import java.util.List;
  68 import java.util.Map;
  69 
  70 /**
  71  * {@link Message} implementation backed by {@link SOAPMessage}.
  72  *
  73  * @author Vivek Pandey
  74  * @author Rama Pulavarthi
  75  */
  76 public class SAAJMessage extends Message {
  77     // flag to switch between representations
  78     private boolean parsedMessage;
  79     // flag to check if Message API is exercised;
  80     private boolean accessedMessage;
  81     private final SOAPMessage sm;
  82 
  83     private MessageHeaders headers;
  84     private List<Element> bodyParts;
  85     private Element payload;
  86 
  87     private String payloadLocalName;
  88     private String payloadNamespace;
  89     private SOAPVersion soapVersion;
  90 
  91     //Collect the attrbutes on the enclosing elements so that the same message can be reproduced without loss of any
  92     // valuable info
  93     private NamedNodeMap bodyAttrs, headerAttrs, envelopeAttrs;
  94 
  95     public SAAJMessage(SOAPMessage sm) {
  96         this.sm = sm;
  97     }
  98 
  99     /**
 100      * This constructor is a convenience and called by the {@link #copy}
 101      *
 102      * @param headers
 103      * @param sm
 104      */
 105     private SAAJMessage(MessageHeaders headers, AttachmentSet as, SOAPMessage sm, SOAPVersion version) {
 106         this.sm = sm;
 107         this.parse();
 108         if(headers == null)
 109             headers = new HeaderList(version);
 110         this.headers = headers;
 111         this.attachmentSet = as;
 112     }
 113 
 114     private void parse() {
 115         if (!parsedMessage) {
 116             try {
 117                 access();
 118                 if (headers == null)
 119                     headers = new HeaderList(getSOAPVersion());
 120                 SOAPHeader header = sm.getSOAPHeader();
 121                 if (header != null) {
 122                     headerAttrs = header.getAttributes();
 123                     Iterator iter = header.examineAllHeaderElements();
 124                     while (iter.hasNext()) {
 125                         headers.add(new SAAJHeader((SOAPHeaderElement) iter.next()));
 126                     }
 127                 }
 128                 attachmentSet = new SAAJAttachmentSet(sm);
 129 
 130                 parsedMessage = true;
 131             } catch (SOAPException e) {
 132                 throw new WebServiceException(e);
 133             }
 134         }
 135     }
 136 
 137     protected void access() {
 138         if (!accessedMessage) {
 139             try {
 140                 envelopeAttrs = sm.getSOAPPart().getEnvelope().getAttributes();
 141                 Node body = sm.getSOAPBody();
 142                 bodyAttrs = body.getAttributes();
 143                 soapVersion = SOAPVersion.fromNsUri(body.getNamespaceURI());
 144                 //cature all the body elements
 145                 bodyParts = DOMUtil.getChildElements(body);
 146                 //we treat payload as the first body part
 147                 payload = bodyParts.size() > 0 ? bodyParts.get(0) : null;
 148                 // hope this is correct. Caching the localname and namespace of the payload should be fine
 149                 // but what about if a Handler replaces the payload with something else? Weel, may be it
 150                 // will be error condition anyway
 151                 if (payload != null) {
 152                     payloadLocalName = payload.getLocalName();
 153                     payloadNamespace = payload.getNamespaceURI();
 154                 }
 155                 accessedMessage = true;
 156             } catch (SOAPException e) {
 157                 throw new WebServiceException(e);
 158             }
 159         }
 160     }
 161 
 162     public boolean hasHeaders() {
 163         parse();
 164         return headers.hasHeaders();
 165     }
 166 
 167     public @NotNull MessageHeaders getHeaders() {
 168         parse();
 169         return headers;
 170     }
 171 
 172     /**
 173      * Gets the attachments of this message
 174      * (attachments live outside a message.)
 175      */
 176     @Override
 177     public @NotNull AttachmentSet getAttachments() {
 178         if (attachmentSet == null) attachmentSet = new SAAJAttachmentSet(sm);
 179         return attachmentSet;
 180     }
 181 
 182     /**
 183      * Optimization hint for the derived class to check
 184      * if we may have some attachments.
 185      */
 186     @Override
 187     protected boolean hasAttachments() {
 188         return !getAttachments().isEmpty();
 189     }
 190 
 191     public @Nullable String getPayloadLocalPart() {
 192         soapBodyFirstChild();
 193         return payloadLocalName;
 194     }
 195 
 196     public String getPayloadNamespaceURI() {
 197         soapBodyFirstChild();
 198         return payloadNamespace;
 199     }
 200 
 201     public boolean hasPayload() {
 202         return soapBodyFirstChild() != null;
 203     }
 204 
 205     private void addAttributes(Element e, NamedNodeMap attrs) {
 206         if(attrs == null)
 207             return;
 208         String elPrefix = e.getPrefix();
 209         for(int i=0; i < attrs.getLength();i++) {
 210             Attr a = (Attr)attrs.item(i);
 211             //check if attr is ns declaration
 212             if("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
 213                 if(elPrefix == null && a.getLocalName().equals("xmlns")) {
 214                     // the target element has already default ns declaration, dont' override it
 215                     continue;
 216                 } else if(elPrefix != null && "xmlns".equals(a.getPrefix()) && elPrefix.equals(a.getLocalName())) {
 217                     //dont bind the prefix to ns again, its already in the target element.
 218                     continue;
 219                 }
 220                 e.setAttributeNS(a.getNamespaceURI(),a.getName(),a.getValue());
 221                 continue;
 222             }
 223             e.setAttributeNS(a.getNamespaceURI(),a.getName(),a.getValue());
 224         }
 225     }
 226 
 227     public Source readEnvelopeAsSource() {
 228         try {
 229             if (!parsedMessage) {
 230                 SOAPEnvelope se = sm.getSOAPPart().getEnvelope();
 231                 return new DOMSource(se);
 232 
 233             } else {
 234                                 SOAPMessage msg = soapVersion.getMessageFactory().createMessage();
 235                 addAttributes(msg.getSOAPPart().getEnvelope(),envelopeAttrs);
 236 
 237                 SOAPBody newBody = msg.getSOAPPart().getEnvelope().getBody();
 238                 addAttributes(newBody, bodyAttrs);
 239                 for (Element part : bodyParts) {
 240                     Node n = newBody.getOwnerDocument().importNode(part, true);
 241                     newBody.appendChild(n);
 242                 }
 243                 addAttributes(msg.getSOAPHeader(),headerAttrs);
 244                 for (Header header : headers.asList()) {
 245                     header.writeTo(msg);
 246                 }
 247                 SOAPEnvelope se = msg.getSOAPPart().getEnvelope();
 248                 return new DOMSource(se);
 249             }
 250         } catch (SOAPException e) {
 251             throw new WebServiceException(e);
 252         }
 253     }
 254 
 255     public SOAPMessage readAsSOAPMessage() throws SOAPException {
 256         if (!parsedMessage) {
 257             return sm;
 258         } else {
 259             SOAPMessage msg = soapVersion.getMessageFactory().createMessage();
 260             addAttributes(msg.getSOAPPart().getEnvelope(),envelopeAttrs);
 261             SOAPBody newBody = msg.getSOAPPart().getEnvelope().getBody();
 262             addAttributes(newBody, bodyAttrs);
 263             for (Element part : bodyParts) {
 264                 Node n = newBody.getOwnerDocument().importNode(part, true);
 265                 newBody.appendChild(n);
 266             }
 267             addAttributes(msg.getSOAPHeader(),headerAttrs);
 268             for (Header header : headers.asList()) {
 269               header.writeTo(msg);
 270             }
 271             for (Attachment att : getAttachments()) {
 272               AttachmentPart part = msg.createAttachmentPart();
 273               part.setDataHandler(att.asDataHandler());
 274               part.setContentId('<' + att.getContentId() + '>');
 275               addCustomMimeHeaders(att, part);
 276               msg.addAttachmentPart(part);
 277             }
 278             msg.saveChanges();
 279             return msg;
 280         }
 281     }
 282 
 283         private void addCustomMimeHeaders(Attachment att, AttachmentPart part) {
 284                 if (att instanceof AttachmentEx) {
 285                         Iterator<AttachmentEx.MimeHeader> allMimeHeaders = ((AttachmentEx) att).getMimeHeaders();
 286                         while (allMimeHeaders.hasNext()) {
 287                                 AttachmentEx.MimeHeader mh = allMimeHeaders.next();
 288                                 String name = mh.getName();
 289                                 if (!"Content-Type".equalsIgnoreCase(name)
 290                                                 && !"Content-Id".equalsIgnoreCase(name)) {
 291                                         part.addMimeHeader(name, mh.getValue());
 292                                 }
 293                         }
 294                 }
 295         }
 296 
 297     public Source readPayloadAsSource() {
 298         access();
 299         return (payload != null) ? new DOMSource(payload) : null;
 300     }
 301 
 302     public <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException {
 303         access();
 304         if (payload != null) {
 305             if(hasAttachments())
 306                 unmarshaller.setAttachmentUnmarshaller(new AttachmentUnmarshallerImpl(getAttachments()));
 307             return (T) unmarshaller.unmarshal(payload);
 308 
 309         }
 310         return null;
 311     }
 312 
 313     /** @deprecated */
 314     public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException {
 315         access();
 316         if (payload != null)
 317             return bridge.unmarshal(payload,hasAttachments()? new AttachmentUnmarshallerImpl(getAttachments()) : null);
 318         return null;
 319     }
 320     public <T> T readPayloadAsJAXB(XMLBridge<T> bridge) throws JAXBException {
 321         access();
 322         if (payload != null)
 323             return bridge.unmarshal(payload,hasAttachments()? new AttachmentUnmarshallerImpl(getAttachments()) : null);
 324         return null;
 325     }
 326 
 327     public XMLStreamReader readPayload() throws XMLStreamException {
 328         return soapBodyFirstChildReader();
 329     }
 330 
 331     public void writePayloadTo(XMLStreamWriter sw) throws XMLStreamException {
 332         access();
 333         try {
 334             for (Element part : bodyParts)
 335                 DOMUtil.serializeNode(part, sw);
 336         } catch (XMLStreamException e) {
 337             throw new WebServiceException(e);
 338         }
 339     }
 340 
 341     public void writeTo(XMLStreamWriter writer) throws XMLStreamException {
 342         try {
 343             writer.writeStartDocument();
 344             if (!parsedMessage) {
 345                 DOMUtil.serializeNode(sm.getSOAPPart().getEnvelope(), writer);
 346             } else {
 347                 SOAPEnvelope env = sm.getSOAPPart().getEnvelope();
 348                 DOMUtil.writeTagWithAttributes(env, writer);
 349                 if (hasHeaders()) {
 350                     if(env.getHeader() != null) {
 351                         DOMUtil.writeTagWithAttributes(env.getHeader(), writer);
 352                     } else {
 353                         writer.writeStartElement(env.getPrefix(), "Header", env.getNamespaceURI());
 354                     }
 355                     for (Header h : headers.asList()) {
 356                         h.writeTo(writer);
 357                     }
 358                     writer.writeEndElement();
 359                 }
 360 
 361                 DOMUtil.serializeNode(sm.getSOAPBody(), writer);
 362                 writer.writeEndElement();
 363             }
 364             writer.writeEndDocument();
 365             writer.flush();
 366         } catch (SOAPException ex) {
 367             throw new XMLStreamException2(ex);
 368             //for now. ask jaxws team what to do.
 369         }
 370     }
 371 
 372     public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler) throws SAXException {
 373         String soapNsUri = soapVersion.nsUri;
 374         if (!parsedMessage) {
 375             DOMScanner ds = new DOMScanner();
 376             ds.setContentHandler(contentHandler);
 377             ds.scan(sm.getSOAPPart());
 378         } else {
 379             contentHandler.setDocumentLocator(NULL_LOCATOR);
 380             contentHandler.startDocument();
 381             contentHandler.startPrefixMapping("S", soapNsUri);
 382             startPrefixMapping(contentHandler, envelopeAttrs,"S");
 383             contentHandler.startElement(soapNsUri, "Envelope", "S:Envelope", getAttributes(envelopeAttrs));
 384             if (hasHeaders()) {
 385                 startPrefixMapping(contentHandler, headerAttrs,"S");
 386                 contentHandler.startElement(soapNsUri, "Header", "S:Header", getAttributes(headerAttrs));
 387                 MessageHeaders headers = getHeaders();
 388                 for (Header h : headers.asList()) {
 389                     h.writeTo(contentHandler, errorHandler);
 390                 }
 391                 endPrefixMapping(contentHandler, headerAttrs,"S");
 392                 contentHandler.endElement(soapNsUri, "Header", "S:Header");
 393 
 394             }
 395             startPrefixMapping(contentHandler, bodyAttrs,"S");
 396             // write the body
 397             contentHandler.startElement(soapNsUri, "Body", "S:Body", getAttributes(bodyAttrs));
 398             writePayloadTo(contentHandler, errorHandler, true);
 399             endPrefixMapping(contentHandler, bodyAttrs,"S");
 400             contentHandler.endElement(soapNsUri, "Body", "S:Body");
 401             endPrefixMapping(contentHandler, envelopeAttrs,"S");
 402             contentHandler.endElement(soapNsUri, "Envelope", "S:Envelope");
 403         }
 404     }
 405     /**
 406      * Gets the Attributes that are not namesapce declarations
 407      * @param attrs
 408      * @return
 409      */
 410     private AttributesImpl getAttributes(NamedNodeMap attrs) {
 411         AttributesImpl atts = new AttributesImpl();
 412         if(attrs == null)
 413             return EMPTY_ATTS;
 414         for(int i=0; i < attrs.getLength();i++) {
 415             Attr a = (Attr)attrs.item(i);
 416             //check if attr is ns declaration
 417             if("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
 418               continue;
 419             }
 420             atts.addAttribute(fixNull(a.getNamespaceURI()),a.getLocalName(),a.getName(),a.getSchemaTypeInfo().getTypeName(),a.getValue());
 421         }
 422         return atts;
 423     }
 424 
 425     /**
 426      * Collects the ns declarations and starts the prefix mapping, consequently the associated endPrefixMapping needs to be called.
 427      * @param contentHandler
 428      * @param attrs
 429      * @param excludePrefix , this is to excldue the global prefix mapping "S" used at the start
 430      * @throws SAXException
 431      */
 432     private void startPrefixMapping(ContentHandler contentHandler, NamedNodeMap attrs, String excludePrefix) throws SAXException {
 433         if(attrs == null)
 434             return;
 435         for(int i=0; i < attrs.getLength();i++) {
 436             Attr a = (Attr)attrs.item(i);
 437             //check if attr is ns declaration
 438             if("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
 439                 if(!fixNull(a.getPrefix()).equals(excludePrefix)) {
 440                     contentHandler.startPrefixMapping(fixNull(a.getPrefix()), a.getNamespaceURI());
 441                 }
 442             }
 443         }
 444     }
 445 
 446     private void endPrefixMapping(ContentHandler contentHandler, NamedNodeMap attrs, String excludePrefix) throws SAXException {
 447         if(attrs == null)
 448             return;
 449         for(int i=0; i < attrs.getLength();i++) {
 450             Attr a = (Attr)attrs.item(i);
 451             //check if attr is ns declaration
 452             if("xmlns".equals(a.getPrefix()) || "xmlns".equals(a.getLocalName())) {
 453                 if(!fixNull(a.getPrefix()).equals(excludePrefix)) {
 454                     contentHandler.endPrefixMapping(fixNull(a.getPrefix()));
 455                 }
 456             }
 457         }
 458     }
 459 
 460     private static String fixNull(String s) {
 461         if(s==null) return "";
 462         else        return s;
 463     }
 464 
 465     private void writePayloadTo(ContentHandler contentHandler, ErrorHandler errorHandler, boolean fragment) throws SAXException {
 466         if(fragment)
 467             contentHandler = new FragmentContentHandler(contentHandler);
 468         DOMScanner ds = new DOMScanner();
 469         ds.setContentHandler(contentHandler);
 470         ds.scan(payload);
 471     }
 472 
 473     /**
 474      * Creates a copy of a {@link com.sun.xml.internal.ws.api.message.Message}.
 475      * <p/>
 476      * <p/>
 477      * This method creates a new {@link com.sun.xml.internal.ws.api.message.Message} whose header/payload/attachments/properties
 478      * are identical to this {@link com.sun.xml.internal.ws.api.message.Message}. Once created, the created {@link com.sun.xml.internal.ws.api.message.Message}
 479      * and the original {@link com.sun.xml.internal.ws.api.message.Message} behaves independently --- adding header/
 480      * attachment to one {@link com.sun.xml.internal.ws.api.message.Message} doesn't affect another {@link com.sun.xml.internal.ws.api.message.Message}
 481      * at all.
 482      * <p/>
 483      * <h3>Design Rationale</h3>
 484      * <p/>
 485      * Since a {@link com.sun.xml.internal.ws.api.message.Message} body is read-once, sometimes
 486      * (such as when you do fail-over, or WS-RM) you need to
 487      * create an idential copy of a {@link com.sun.xml.internal.ws.api.message.Message}.
 488      * <p/>
 489      * <p/>
 490      * The actual copy operation depends on the layout
 491      * of the data in memory, hence it's best to be done by
 492      * the {@link com.sun.xml.internal.ws.api.message.Message} implementation itself.
 493      */
 494     public Message copy() {
 495         Message result = null;
 496         try {
 497             access();
 498             if (!parsedMessage) {
 499                 result = new SAAJMessage(readAsSOAPMessage());
 500             } else {
 501                 SOAPMessage msg = soapVersion.getMessageFactory().createMessage();
 502                 SOAPBody newBody = msg.getSOAPPart().getEnvelope().getBody();
 503                 for (Element part : bodyParts) {
 504                     Node n = newBody.getOwnerDocument().importNode(part, true);
 505                     newBody.appendChild(n);
 506                 }
 507                 addAttributes(newBody, bodyAttrs);
 508                 result = new SAAJMessage(getHeaders(), getAttachments(), msg, soapVersion);
 509             }
 510             return result.copyFrom(this);
 511         } catch (SOAPException e) {
 512             throw new WebServiceException(e);
 513         }
 514     }
 515     private static final AttributesImpl EMPTY_ATTS = new AttributesImpl();
 516     private static final LocatorImpl NULL_LOCATOR = new LocatorImpl();
 517 
 518     protected static class SAAJAttachment implements AttachmentEx {
 519 
 520         final AttachmentPart ap;
 521 
 522         String contentIdNoAngleBracket;
 523 
 524         public SAAJAttachment(AttachmentPart part) {
 525             this.ap = part;
 526         }
 527 
 528         /**
 529          * Content ID of the attachment. Uniquely identifies an attachment.
 530          */
 531         public String getContentId() {
 532             if (contentIdNoAngleBracket == null) {
 533                 contentIdNoAngleBracket = ap.getContentId();
 534                 if (contentIdNoAngleBracket != null && contentIdNoAngleBracket.charAt(0) == '<')
 535                     contentIdNoAngleBracket = contentIdNoAngleBracket.substring(1, contentIdNoAngleBracket.length()-1);
 536             }
 537             return contentIdNoAngleBracket;
 538         }
 539 
 540         /**
 541          * Gets the MIME content-type of this attachment.
 542          */
 543         public String getContentType() {
 544             return ap.getContentType();
 545         }
 546 
 547         /**
 548          * Gets the attachment as an exact-length byte array.
 549          */
 550         public byte[] asByteArray() {
 551             try {
 552                 return ap.getRawContentBytes();
 553             } catch (SOAPException e) {
 554                 throw new WebServiceException(e);
 555             }
 556         }
 557 
 558         /**
 559          * Gets the attachment as a {@link javax.activation.DataHandler}.
 560          */
 561         public DataHandler asDataHandler() {
 562             try {
 563                 return ap.getDataHandler();
 564             } catch (SOAPException e) {
 565                 throw new WebServiceException(e);
 566             }
 567         }
 568 
 569         /**
 570          * Gets the attachment as a {@link javax.xml.transform.Source}.
 571          * Note that there's no guarantee that the attachment is actually an XML.
 572          */
 573         public Source asSource() {
 574             try {
 575                 return new StreamSource(ap.getRawContent());
 576             } catch (SOAPException e) {
 577                 throw new WebServiceException(e);
 578             }
 579         }
 580 
 581         /**
 582          * Obtains this attachment as an {@link java.io.InputStream}.
 583          */
 584         public InputStream asInputStream() {
 585             try {
 586                 return ap.getRawContent();
 587             } catch (SOAPException e) {
 588                 throw new WebServiceException(e);
 589             }
 590         }
 591 
 592         /**
 593          * Writes the contents of the attachment into the given stream.
 594          */
 595         public void writeTo(OutputStream os) throws IOException {
 596             try {
 597                 ASCIIUtility.copyStream(ap.getRawContent(), os);
 598             } catch (SOAPException e) {
 599                 throw new WebServiceException(e);
 600             }
 601         }
 602 
 603         /**
 604          * Writes this attachment to the given {@link javax.xml.soap.SOAPMessage}.
 605          */
 606         public void writeTo(SOAPMessage saaj) {
 607             saaj.addAttachmentPart(ap);
 608         }
 609 
 610         AttachmentPart asAttachmentPart(){
 611             return ap;
 612         }
 613 
 614                 public Iterator<MimeHeader> getMimeHeaders() {
 615                         final Iterator it = ap.getAllMimeHeaders();
 616                         return new Iterator<MimeHeader>() {
 617                                 public boolean hasNext() {
 618                                         return it.hasNext();
 619                                 }
 620 
 621                                 public MimeHeader next() {
 622                                         final javax.xml.soap.MimeHeader mh = (javax.xml.soap.MimeHeader) it.next();
 623                                         return new MimeHeader() {
 624                                                 public String getName() {
 625                                                         return mh.getName();
 626                                                 }
 627 
 628                                                 public String getValue() {
 629                                                         return mh.getValue();
 630                                                 }
 631                                         };
 632                                 }
 633 
 634                                 public void remove() {
 635                                         throw new UnsupportedOperationException();
 636                                 }
 637                         };
 638                 }
 639     }
 640 
 641     /**
 642      * {@link AttachmentSet} for SAAJ.
 643      *
 644      * SAAJ wants '&lt;' and '>' for the content ID, but {@link AttachmentSet}
 645      * doesn't. S this class also does the conversion between them.
 646      */
 647     protected static class SAAJAttachmentSet implements AttachmentSet {
 648 
 649         private Map<String, Attachment> attMap;
 650         private Iterator attIter;
 651 
 652         public SAAJAttachmentSet(SOAPMessage sm) {
 653             attIter = sm.getAttachments();
 654         }
 655 
 656         /**
 657          * Gets the attachment by the content ID.
 658          *
 659          * @return null
 660          *         if no such attachment exist.
 661          */
 662         public Attachment get(String contentId) {
 663             // if this is the first time then create the attachment Map
 664             if (attMap == null) {
 665                 if (!attIter.hasNext())
 666                     return null;
 667                 attMap = createAttachmentMap();
 668             }
 669             if(contentId.charAt(0) != '<'){
 670                 return attMap.get('<'+contentId+'>');
 671             }
 672             return attMap.get(contentId);
 673         }
 674 
 675         public boolean isEmpty() {
 676             if(attMap!=null)
 677                 return attMap.isEmpty();
 678             else
 679                 return !attIter.hasNext();
 680         }
 681 
 682         /**
 683          * Returns an iterator over a set of elements of type T.
 684          *
 685          * @return an Iterator.
 686          */
 687         public Iterator<Attachment> iterator() {
 688             if (attMap == null) {
 689                 attMap = createAttachmentMap();
 690             }
 691             return attMap.values().iterator();
 692         }
 693 
 694         private Map<String, Attachment> createAttachmentMap() {
 695             HashMap<String, Attachment> map = new HashMap<String, Attachment>();
 696             while (attIter.hasNext()) {
 697                 AttachmentPart ap = (AttachmentPart) attIter.next();
 698                 map.put(ap.getContentId(), new SAAJAttachment(ap));
 699             }
 700             return map;
 701         }
 702 
 703         public void add(Attachment att) {
 704             attMap.put('<'+att.getContentId()+'>', att);
 705         }
 706     }
 707 
 708     public SOAPVersion getSOAPVersion() {
 709         return soapVersion;
 710     }
 711 
 712     private XMLStreamReader soapBodyFirstChildReader;
 713 
 714     /**
 715      * This allow the subclass to retain the XMLStreamReader.
 716      */
 717     protected XMLStreamReader getXMLStreamReader(SOAPElement soapElement) {
 718         return null;
 719     }
 720 
 721     protected XMLStreamReader createXMLStreamReader(SOAPElement soapElement) {
 722         DOMStreamReader dss = new DOMStreamReader();
 723         dss.setCurrentNode(soapElement);
 724         return dss;
 725     }
 726 
 727     protected XMLStreamReader soapBodyFirstChildReader() {
 728         if (soapBodyFirstChildReader != null) return soapBodyFirstChildReader;
 729         soapBodyFirstChild();
 730         if (soapBodyFirstChild != null) {
 731             soapBodyFirstChildReader = getXMLStreamReader(soapBodyFirstChild);
 732             if (soapBodyFirstChildReader == null) soapBodyFirstChildReader =
 733                 createXMLStreamReader(soapBodyFirstChild);
 734             if (soapBodyFirstChildReader.getEventType() == XMLStreamReader.START_DOCUMENT) {
 735                 try {
 736                     while(soapBodyFirstChildReader.getEventType() != XMLStreamReader.START_ELEMENT)
 737                         soapBodyFirstChildReader.next();
 738                 } catch (XMLStreamException e) {
 739                     throw new RuntimeException(e);
 740                 }
 741             }
 742             return soapBodyFirstChildReader;
 743         } else {
 744             payloadLocalName = null;
 745             payloadNamespace = null;
 746             return null;
 747         }
 748     }
 749 
 750     private SOAPElement soapBodyFirstChild;
 751 
 752     SOAPElement soapBodyFirstChild() {
 753         if (soapBodyFirstChild != null) return soapBodyFirstChild;
 754         try {
 755             boolean foundElement = false;
 756             for (Node n = sm.getSOAPBody().getFirstChild(); n != null && !foundElement; n = n.getNextSibling()) {
 757                 if (n.getNodeType() == Node.ELEMENT_NODE) {
 758                     foundElement = true;
 759                     if (n instanceof SOAPElement) {
 760                         soapBodyFirstChild = (SOAPElement) n;
 761                         payloadLocalName = soapBodyFirstChild.getLocalName();
 762                         payloadNamespace = soapBodyFirstChild.getNamespaceURI();
 763                         return soapBodyFirstChild;
 764                     }
 765                 }
 766             }
 767             if(foundElement) for(Iterator i = sm.getSOAPBody().getChildElements(); i.hasNext();){
 768                 Object o = i.next();
 769                 if (o instanceof SOAPElement) {
 770                     soapBodyFirstChild = (SOAPElement)o;
 771                     payloadLocalName = soapBodyFirstChild.getLocalName();
 772                     payloadNamespace = soapBodyFirstChild.getNamespaceURI();
 773                     return soapBodyFirstChild;
 774                 }
 775             }
 776         } catch (SOAPException e) {
 777             throw new RuntimeException(e);
 778         }
 779         return soapBodyFirstChild;
 780     }
 781 }