001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.servicemix.soap.marshalers;
018    
019    import java.io.ByteArrayOutputStream;
020    import java.io.FilterOutputStream;
021    import java.io.IOException;
022    import java.io.OutputStream;
023    import java.net.URI;
024    import java.util.ArrayList;
025    import java.util.Enumeration;
026    import java.util.Iterator;
027    import java.util.List;
028    import java.util.Map;
029    import java.util.Properties;
030    
031    import javax.activation.DataHandler;
032    import javax.mail.Header;
033    import javax.mail.Session;
034    import javax.mail.internet.MimeBodyPart;
035    import javax.mail.internet.MimeMessage;
036    import javax.mail.internet.MimeMultipart;
037    import javax.xml.XMLConstants;
038    import javax.xml.namespace.QName;
039    import javax.xml.parsers.ParserConfigurationException;
040    import javax.xml.stream.XMLStreamException;
041    import javax.xml.stream.XMLStreamReader;
042    import javax.xml.stream.XMLStreamWriter;
043    import javax.xml.transform.Source;
044    import javax.xml.transform.TransformerException;
045    import javax.xml.transform.dom.DOMSource;
046    import javax.xml.transform.stream.StreamResult;
047    
048    import org.apache.servicemix.jbi.jaxp.SourceTransformer;
049    import org.apache.servicemix.jbi.jaxp.W3CDOMStreamReader;
050    import org.apache.servicemix.jbi.jaxp.XMLStreamHelper;
051    import org.apache.servicemix.jbi.util.ByteArrayDataSource;
052    import org.apache.servicemix.soap.SoapFault;
053    import org.w3c.dom.DocumentFragment;
054    import org.w3c.dom.Element;
055    import org.w3c.dom.Node;
056    import org.w3c.dom.NodeList;
057    import org.xml.sax.SAXException;
058    
059    /**
060     * 
061     * @author Guillaume Nodet
062     * @version $Revision: 1.5 $
063     * @since 3.0
064     */
065    public class SoapWriter {
066    
067        public static final String SOAP_PART_ID = "soap-request";
068    
069        private SoapMessage message;
070    
071        private String contentType;
072    
073        private SoapMarshaler marshaler;
074    
075        private MimeMultipart parts;
076    
077        public SoapWriter(SoapMarshaler marshaler, SoapMessage message) {
078            this.marshaler = marshaler;
079            this.message = message;
080            this.contentType = prepare();
081        }
082    
083        public String getContentType() {
084            return contentType;
085        }
086    
087        public void write(OutputStream out) throws Exception {
088            if (message.hasAttachments()) {
089                writeMultipartMessage(out);
090            } else {
091                writeSimpleMessage(out);
092            }
093        }
094    
095        private String prepare() {
096            if (message.hasAttachments()) {
097                parts = new MimeMultipart("related; type=\"text/xml\"; start=\"<" + SOAP_PART_ID + ">\"");
098                return parts.getContentType();
099            } else {
100                return "text/xml";
101            }
102        }
103    
104        private void writeSimpleMessage(OutputStream out) throws Exception {
105            if (message.getDocument() != null) {
106                marshaler.getSourceTransformer().toResult(new DOMSource(message.getDocument()), new StreamResult(out));
107                return;
108            }
109            XMLStreamWriter writer = marshaler.getOutputFactory().createXMLStreamWriter(out);
110            writer.writeStartDocument();
111            if (marshaler.isSoap()) {
112                writeSoapEnvelope(writer);
113            } else {
114                if (message.hasHeaders()) {
115                    throw new IllegalStateException("SOAP headers found on non-soap message");
116                }
117                if (message.getFault() != null) {
118                    if (message.getFault().getDetails() != null) {
119                        XMLStreamReader reader = marshaler.getSourceTransformer().toXMLStreamReader(
120                                message.getFault().getDetails());
121                        XMLStreamHelper.copy(reader, writer);
122                    } else {
123                        throw new IllegalStateException("Cannot write non xml faults for non soap messages");
124                    }
125                } else if (message.getSource() != null) {
126                    writeContents(writer);
127                }
128            }
129            writer.writeEndDocument();
130            writer.flush();
131        }
132    
133        private void writeMultipartMessage(OutputStream out) throws Exception {
134            Session session = Session.getDefaultInstance(new Properties(), null);
135            MimeMessage mime = new MimeMessage(session);
136            // Add soap part
137            MimeBodyPart soapPart = new MimeBodyPart();
138            soapPart.setContentID("<" + SOAP_PART_ID + ">");
139            ByteArrayOutputStream baos = new ByteArrayOutputStream();
140            writeSimpleMessage(baos);
141            soapPart.setDataHandler(new DataHandler(new ByteArrayDataSource(baos.toByteArray(), "text/xml")));
142            parts.addBodyPart(soapPart);
143            // Add attachments
144            for (Iterator itr = message.getAttachments().entrySet().iterator(); itr.hasNext();) {
145                Map.Entry entry = (Map.Entry) itr.next();
146                String id = (String) entry.getKey();
147                DataHandler dh = (DataHandler) entry.getValue();
148                MimeBodyPart part = new MimeBodyPart();
149                part.setDataHandler(dh);
150                part.setContentID("<" + id + ">");
151                parts.addBodyPart(part);
152            }
153            mime.setContent(parts);
154            mime.setHeader(SoapMarshaler.MIME_CONTENT_TYPE, getContentType());
155            // We do not want headers, so 
156            //  * retrieve all headers
157            //  * skip first 2 bytes (CRLF)
158            mime.saveChanges();
159            Enumeration headersEnum = mime.getAllHeaders();
160            List headersList = new ArrayList();
161            while (headersEnum.hasMoreElements()) {
162                headersList.add(((Header) headersEnum.nextElement()).getName().toLowerCase());
163            }
164            String[] headers = (String[]) headersList.toArray(new String[0]);
165            // Skip first 2 bytes
166            OutputStream os = new FilterOutputStream(out) {
167                private int nb = 0;
168                public void write(int b) throws IOException {
169                    if (++nb > 2) {
170                        super.write(b);
171                    }
172                }
173            };
174            // Write
175            mime.writeTo(os, headers);
176        }
177    
178        public void writeSoapEnvelope(XMLStreamWriter writer) throws Exception {
179            QName envelope = getEnvelopeName();
180            String soapUri = envelope.getNamespaceURI();
181            String soapPrefix = envelope.getPrefix();
182            writer.setPrefix(soapPrefix, soapUri);
183            writer.writeStartElement(soapPrefix, SoapMarshaler.ENVELOPE, soapUri);
184            if (!marshaler.isRepairingNamespace()) {
185                writer.writeNamespace(soapPrefix, soapUri);
186                // XMLStreamHelper.writeNamespacesExcludingPrefixAndNamespace(out, in, soapPrefix, soapUri);
187            }
188            // Write Header
189            if (message.getHeaders() != null && message.getHeaders().size() > 0) {
190                writer.writeStartElement(soapPrefix, SoapMarshaler.HEADER, soapUri);
191                for (Iterator it = message.getHeaders().values().iterator(); it.hasNext();) {
192                    DocumentFragment df = (DocumentFragment) it.next();
193                    Element e = (Element) df.getFirstChild();
194                    XMLStreamHelper.copy(new W3CDOMStreamReader(e), writer);
195                }
196                writer.writeEndElement();
197            }
198            // Write Body
199            writer.writeStartElement(soapPrefix, SoapMarshaler.BODY, soapUri);
200            if (message.getFault() != null) {
201                writeFault(writer);
202            } else if (message.getSource() != null) {
203                writeContents(writer);
204            }
205            writer.writeEndElement();
206            writer.writeEndElement();
207        }
208    
209        private void writeContents(XMLStreamWriter writer) throws Exception {
210            XMLStreamReader reader = marshaler.getSourceTransformer().toXMLStreamReader(message.getSource());
211            XMLStreamHelper.copy(reader, writer);
212        }
213    
214        private void writeFault(XMLStreamWriter writer) throws Exception {
215            QName envelope = getEnvelopeName();
216            String soapUri = envelope.getNamespaceURI();
217            if (SoapMarshaler.SOAP_11_URI.equals(soapUri)) {
218                writeSoap11Fault(writer);
219            } else if (SoapMarshaler.SOAP_12_URI.equals(soapUri)) {
220                writeSoap12Fault(writer);
221            } else {
222                throw new IllegalStateException("Unknown soap namespace: " + soapUri);
223            }
224        }
225    
226        private void writeSoap11Fault(XMLStreamWriter writer) throws Exception {
227            QName envelope = getEnvelopeName();
228            String soapUri = envelope.getNamespaceURI();
229            String soapPrefix = envelope.getPrefix();
230            writer.setPrefix(soapPrefix, soapUri);
231            SoapFault fault = message.getFault();
232            fault.translateCodeTo11();
233    
234            writer.writeStartElement(soapPrefix, SoapMarshaler.FAULT, soapUri);
235            QName code = fault.getCode();
236            if (code != null) {
237                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_11_FAULTCODE);
238                XMLStreamHelper.writeTextQName(writer, code);
239                writer.writeEndElement();
240            }
241            String reason = fault.getReason();
242            if (reason == null && fault.getCause() != null) {
243                reason = fault.getCause().toString();
244            }
245            XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_11_FAULTSTRING);
246            if (reason != null) {
247                writer.writeCharacters(reason);
248            }
249            writer.writeEndElement();
250            URI node = fault.getNode();
251            if (node != null) {
252                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_11_FAULTACTOR);
253                writer.writeCharacters(node.toString());
254                writer.writeEndElement();
255            }
256            Source details = fault.getDetails();
257            if (details != null) {
258                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_11_FAULTDETAIL);
259                writeDetails(writer, details);
260                writer.writeEndElement();
261            }
262    
263            writer.writeEndElement();
264        }
265    
266        private void writeSoap12Fault(XMLStreamWriter writer) throws Exception {
267            QName envelope = getEnvelopeName();
268            String soapUri = envelope.getNamespaceURI();
269            String soapPrefix = envelope.getPrefix();
270            writer.setPrefix(soapPrefix, soapUri);
271            SoapFault fault = message.getFault();
272            fault.translateCodeTo12();
273            
274            writer.writeStartElement(soapPrefix, SoapMarshaler.FAULT, soapUri);
275            QName code = fault.getCode();
276            if (code != null) {
277                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTCODE);
278                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTVALUE);
279                XMLStreamHelper.writeTextQName(writer, code);
280                writer.writeEndElement();
281                QName subcode = fault.getSubcode();
282                if (subcode != null) {
283                    XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTSUBCODE);
284                    XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTVALUE);
285                    XMLStreamHelper.writeTextQName(writer, subcode);
286                    writer.writeEndElement();
287                    writer.writeEndElement();
288                }
289                writer.writeEndElement();
290            }
291            String reason = fault.getReason();
292            if (reason == null && fault.getCause() != null) {
293                reason = fault.getCause().toString();
294            }
295            XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTREASON);
296            XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTTEXT);
297            writer.writeAttribute(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI, "lang", "en");
298            if (reason != null) {
299                writer.writeCharacters(reason);
300            }
301            writer.writeEndElement();
302            writer.writeEndElement();
303            URI node = fault.getNode();
304            if (node != null) {
305                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTNODE);
306                writer.writeCharacters(node.toString());
307                writer.writeEndElement();
308            }
309    
310            URI role = fault.getRole();
311            if (role != null) {
312                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTROLE);
313                writer.writeCharacters(role.toString());
314                writer.writeEndElement();
315            }
316    
317            Source details = fault.getDetails();
318            if (details != null) {
319                XMLStreamHelper.writeStartElement(writer, SoapMarshaler.SOAP_12_FAULTDETAIL);
320                writeDetails(writer, details);
321                writer.writeEndElement();
322            }
323    
324            writer.writeEndElement();
325        }
326    
327        private void writeDetails(XMLStreamWriter writer, Source details) throws ParserConfigurationException, IOException, SAXException, TransformerException, XMLStreamException {
328            SourceTransformer st = new SourceTransformer();
329            DOMSource domDetails = st.toDOMSource(details);
330            Node detailsNode = domDetails.getNode().getFirstChild();
331            if ( SoapMarshaler.MULTIPLE_DETAILS_NODE_WRAPPER.equals(detailsNode.getNodeName()) ) {
332                NodeList children = detailsNode.getChildNodes();
333                for ( int i = 0; i < children.getLength(); i++ ) {
334                    Node node = children.item(i);
335                    if ( node.getNodeType() == Node.ELEMENT_NODE ) {
336                        XMLStreamReader reader = marshaler.getSourceTransformer().toXMLStreamReader(new DOMSource(node));
337                        XMLStreamHelper.copy(reader, writer);
338                    }
339                }
340            } else {
341                XMLStreamReader reader = marshaler.getSourceTransformer().toXMLStreamReader(details);
342                XMLStreamHelper.copy(reader, writer);
343            }
344        }
345        
346        protected QName getEnvelopeName() {
347            QName name = message.getEnvelopeName();
348            if (name == null) {
349                name = new QName(marshaler.getSoapUri(), SoapMarshaler.ENVELOPE, marshaler.getPrefix());
350            } else if (name.getPrefix() == null) {
351                name = new QName(name.getNamespaceURI(), name.getLocalPart(), marshaler.getPrefix());
352            }
353            return name;
354        }
355        
356    }