/* * Copyright 2005-2006 Sun Microsystems, Inc. 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.xml.internal.ws.message.saaj; import com.sun.istack.internal.NotNull; import com.sun.istack.internal.XMLStreamException2; import com.sun.xml.internal.bind.api.Bridge; import com.sun.xml.internal.bind.unmarshaller.DOMScanner; import com.sun.xml.internal.ws.api.SOAPVersion; import com.sun.xml.internal.ws.api.message.Attachment; import com.sun.xml.internal.ws.api.message.AttachmentSet; import com.sun.xml.internal.ws.api.message.HeaderList; import com.sun.xml.internal.ws.api.message.Message; import com.sun.xml.internal.ws.api.message.Packet; import com.sun.xml.internal.ws.message.AttachmentUnmarshallerImpl; import com.sun.xml.internal.ws.streaming.DOMStreamReader; import com.sun.xml.internal.ws.util.DOMUtil; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.ContentHandler; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import javax.activation.DataHandler; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.soap.AttachmentPart; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPException; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPHeaderElement; import javax.xml.soap.SOAPMessage; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import javax.xml.ws.WebServiceException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; /** * {@link Message} implementation backed by {@link SOAPMessage}. * * @author Vivek Pandey */ public class SAAJMessage extends Message { private final SOAPMessage sm; private HeaderList headers; private String payloadLocalName; private String payloadNamspace; private List<Element> bodyParts; private Element payload; private boolean parsedHeader; public SAAJMessage(SOAPMessage sm) { this.sm = sm; try { Node body = sm.getSOAPBody(); //cature all the body elements bodyParts = DOMUtil.getChildElements(body); //we treat payload as the first body part payload = bodyParts.size() > 0? bodyParts.get(0):null; // hope this is correct. Caching the localname and namespace of the payload should be fine // but what about if a Handler replaces the payload with something else? Weel, may be it // will be error condition anyway if (payload != null) { payloadLocalName = payload.getLocalName(); payloadNamspace = payload.getNamespaceURI(); } } catch (SOAPException e) { throw new WebServiceException(e); } } /** * This constructor is a convenience and called by the {@link #copy} * @param headers * @param sm */ private SAAJMessage(HeaderList headers, AttachmentSet as, SOAPMessage sm) { this(sm); this.headers = headers; this.attachmentSet = as; } public boolean hasHeaders() { return getHeaders().size() > 0; } /** * Gets all the headers of this message. * * @return always return the same non-null object. */ public HeaderList getHeaders() { if (parsedHeader) return headers; if (headers == null) headers = new HeaderList(); try { SOAPHeader header = sm.getSOAPHeader(); if(header!=null) { Iterator iter = header.examineAllHeaderElements(); while (iter.hasNext()) { headers.add(new SAAJHeader((SOAPHeaderElement) iter.next())); } } parsedHeader = true; } catch (SOAPException e) { e.printStackTrace(); } return headers; } /** * Gets the attachments of this message * (attachments live outside a message.) */ @Override @NotNull public AttachmentSet getAttachments() { if (attachmentSet == null) attachmentSet = new SAAJAttachmentSet(sm); return attachmentSet; } protected boolean hasAttachments() { return !getAttachments().isEmpty(); } /** * Gets the local name of the payload element. */ public String getPayloadLocalPart() { return payloadLocalName; } /** * Gets the namespace URI of the payload element. */ public String getPayloadNamespaceURI() { return payloadNamspace; } public boolean hasPayload() { return payloadNamspace!=null; } /** * Consumes this message including the envelope. * returns it as a {@link javax.xml.transform.Source} object. */ public Source readEnvelopeAsSource() { try { SOAPEnvelope se = sm .getSOAPPart().getEnvelope(); return new DOMSource(se); } catch (SOAPException e) { throw new WebServiceException(e); } } /** * Returns the payload as a {@link javax.xml.transform.Source} object. * * Can't really give all the body parts inside soapenv:Body as Source * cant take only one part. * * <p/> * This consumes the message. */ public Source readPayloadAsSource() { return (payload != null) ? new DOMSource(payload) : null; } /** * Creates the equivalent {@link javax.xml.soap.SOAPMessage} from this message. * <p/> * This consumes the message. */ public SOAPMessage readAsSOAPMessage() { return sm; } public SOAPMessage readAsSOAPMessage(Packet packet, boolean inbound) throws SOAPException { return sm; } /** * Reads the payload as a JAXB object by using the given unmarshaller. * <p/> * This consumes the message. */ public <T> T readPayloadAsJAXB(Unmarshaller unmarshaller) throws JAXBException { try { Node pn = sm.getSOAPBody().getFirstChild(); if (pn != null) return (T) unmarshaller.unmarshal(pn); return null; } catch (SOAPException e) { throw new WebServiceException(e); } } public <T> T readPayloadAsJAXB(Bridge<T> bridge) throws JAXBException { try { Node pn = sm.getSOAPBody().getFirstChild(); if (pn != null) return bridge.unmarshal(pn, new AttachmentUnmarshallerImpl(getAttachments())); return null; } catch (SOAPException e) { throw new WebServiceException(e); } } /** * Reads the payload as a {@link javax.xml.stream.XMLStreamReader} * <p/> * This consumes the message. */ public XMLStreamReader readPayload() throws XMLStreamException { if(payload==null) return null; DOMStreamReader dss = new DOMStreamReader(); dss.setCurrentNode(payload); dss.nextTag(); assert dss.getEventType()==XMLStreamReader.START_ELEMENT; return dss; } /** * Writes the payload to StAX. * <p/> * This method writes just the payload of the message to the writer. * This consumes the message. */ public void writePayloadTo(XMLStreamWriter sw) { try { for(Element part: bodyParts) DOMUtil.serializeNode(part, sw); } catch (XMLStreamException e) { throw new WebServiceException(e); } } public void writeTo(ContentHandler contentHandler, ErrorHandler errorHandler) throws SAXException { DOMScanner ds = new DOMScanner(); ds.setContentHandler(contentHandler); ds.scan(sm.getSOAPPart()); } /** * Creates a copy of a {@link com.sun.xml.internal.ws.api.message.Message}. * <p/> * <p/> * This method creates a new {@link com.sun.xml.internal.ws.api.message.Message} whose header/payload/attachments/properties * 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} * and the original {@link com.sun.xml.internal.ws.api.message.Message} behaves independently --- adding header/ * 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} * at all. * <p/> * <h3>Design Rationale</h3> * <p/> * Since a {@link com.sun.xml.internal.ws.api.message.Message} body is read-once, sometimes * (such as when you do fail-over, or WS-RM) you need to * create an idential copy of a {@link com.sun.xml.internal.ws.api.message.Message}. * <p/> * <p/> * The actual copy operation depends on the layout * of the data in memory, hence it's best to be done by * the {@link com.sun.xml.internal.ws.api.message.Message} implementation itself. */ public Message copy() { try { SOAPBody sb = sm.getSOAPPart().getEnvelope().getBody(); SOAPMessage msg = SOAPVersion.fromNsUri(sb.getNamespaceURI()).saajMessageFactory.createMessage(); SOAPBody newBody = msg.getSOAPPart().getEnvelope().getBody(); for(Element part: bodyParts){ Node n = newBody.getOwnerDocument().importNode(part, true); newBody.appendChild(n); } return new SAAJMessage(getHeaders(), getAttachments(), msg); } catch (SOAPException e) { throw new WebServiceException(e); } } private class SAAJAttachment implements Attachment { AttachmentPart ap; public SAAJAttachment(AttachmentPart part) { this.ap = part; } /** * Content ID of the attachment. Uniquely identifies an attachment. */ public String getContentId() { return ap.getContentId(); } /** * Gets the MIME content-type of this attachment. */ public String getContentType() { return ap.getContentType(); } /** * Gets the attachment as an exact-length byte array. */ public byte[] asByteArray() { try { return ap.getRawContentBytes(); } catch (SOAPException e) { throw new WebServiceException(e); } } /** * Gets the attachment as a {@link javax.activation.DataHandler}. */ public DataHandler asDataHandler() { try { return ap.getDataHandler(); } catch (SOAPException e) { throw new WebServiceException(e); } } /** * Gets the attachment as a {@link javax.xml.transform.Source}. * Note that there's no guarantee that the attachment is actually an XML. */ public Source asSource() { try { return new StreamSource(ap.getRawContent()); } catch (SOAPException e) { throw new WebServiceException(e); } } /** * Obtains this attachment as an {@link java.io.InputStream}. */ public InputStream asInputStream() { try { return ap.getRawContent(); } catch (SOAPException e) { throw new WebServiceException(e); } } /** * Writes the contents of the attachment into the given stream. */ public void writeTo(OutputStream os) throws IOException { os.write(asByteArray()); } /** * Writes this attachment to the given {@link javax.xml.soap.SOAPMessage}. */ public void writeTo(SOAPMessage saaj) { saaj.addAttachmentPart(ap); } AttachmentPart asAttachmentPart(){ return ap; } } /** * {@link AttachmentSet} for SAAJ. * * SAAJ wants '<' and '>' for the content ID, but {@link AttachmentSet} * doesn't. S this class also does the conversion between them. */ private class SAAJAttachmentSet implements AttachmentSet { private Map<String, Attachment> attMap; private Iterator attIter; public SAAJAttachmentSet(SOAPMessage sm) { attIter = sm.getAttachments(); } /** * Gets the attachment by the content ID. * * @return null * if no such attachment exist. */ public Attachment get(String contentId) { // if this is the first time then create the attachment Map if (attMap == null) { if (!attIter.hasNext()) return null; attMap = createAttachmentMap(); } if(contentId.charAt(0) != '<'){ return attMap.get('<'+contentId+'>'); } return attMap.get(contentId); } public boolean isEmpty() { if(attMap!=null) return attMap.isEmpty(); else return !attIter.hasNext(); } /** * Returns an iterator over a set of elements of type T. * * @return an Iterator. */ public Iterator<Attachment> iterator() { if (attMap == null) { attMap = createAttachmentMap(); } return attMap.values().iterator(); } private Map<String, Attachment> createAttachmentMap() { HashMap<String, Attachment> map = new HashMap<String, Attachment>(); while (attIter.hasNext()) { AttachmentPart ap = (AttachmentPart) attIter.next(); map.put(ap.getContentId(), new SAAJAttachment(ap)); } return map; } public void add(Attachment att) { attMap.put('<'+att.getContentId()+'>', att); } } public void writeTo( XMLStreamWriter writer ) throws XMLStreamException { try { writer.writeStartDocument(); SOAPEnvelope env = sm.getSOAPPart().getEnvelope(); DOMUtil.writeTagWithAttributes(env, writer); if(hasHeaders()) { writer.writeStartElement(env.getPrefix(),"Header",env.getNamespaceURI()); int len = headers.size(); for( int i=0; i<len; i++ ) { headers.get(i).writeTo(writer); } writer.writeEndElement(); } DOMUtil.serializeNode(sm.getSOAPBody(),writer); writer.writeEndElement(); writer.writeEndDocument(); writer.flush(); } catch (SOAPException ex) { throw new XMLStreamException2(ex); //for now. ask jaxws team what to do. } } }