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