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
26package com.sun.xml.internal.ws.message.saaj;
27
28import com.sun.istack.internal.FragmentContentHandler;
29import com.sun.istack.internal.NotNull;
30import com.sun.istack.internal.Nullable;
31import com.sun.istack.internal.XMLStreamException2;
32import com.sun.xml.internal.bind.api.Bridge;
33import com.sun.xml.internal.bind.unmarshaller.DOMScanner;
34import com.sun.xml.internal.ws.api.SOAPVersion;
35import com.sun.xml.internal.ws.api.message.*;
36import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl;
37import com.sun.xml.internal.ws.spi.db.XMLBridge;
38import com.sun.xml.internal.ws.streaming.DOMStreamReader;
39import com.sun.xml.internal.ws.util.ASCIIUtility;
40import com.sun.xml.internal.ws.util.DOMUtil;
41import org.w3c.dom.Attr;
42import org.w3c.dom.Element;
43import org.w3c.dom.NamedNodeMap;
44import org.w3c.dom.Node;
45import org.xml.sax.ContentHandler;
46import org.xml.sax.ErrorHandler;
47import org.xml.sax.SAXException;
48import org.xml.sax.helpers.AttributesImpl;
49import org.xml.sax.helpers.LocatorImpl;
50
51import javax.activation.DataHandler;
52import javax.xml.bind.JAXBException;
53import javax.xml.bind.Unmarshaller;
54import javax.xml.soap.*;
55import javax.xml.stream.XMLStreamException;
56import javax.xml.stream.XMLStreamReader;
57import javax.xml.stream.XMLStreamWriter;
58import javax.xml.transform.Source;
59import javax.xml.transform.dom.DOMSource;
60import javax.xml.transform.stream.StreamSource;
61import javax.xml.ws.WebServiceException;
62import java.io.IOException;
63import java.io.InputStream;
64import java.io.OutputStream;
65import java.util.HashMap;
66import java.util.Iterator;
67import java.util.List;
68import java.util.Map;
69
70/**
71 * {@link Message} implementation backed by {@link SOAPMessage}.
72 *
73 * @author Vivek Pandey
74 * @author Rama Pulavarthi
75 */
76public 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}
782