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.mail.internet.MimeUtility;
038 import javax.xml.XMLConstants;
039 import javax.xml.namespace.QName;
040 import javax.xml.parsers.ParserConfigurationException;
041 import javax.xml.stream.XMLStreamException;
042 import javax.xml.stream.XMLStreamReader;
043 import javax.xml.stream.XMLStreamWriter;
044 import javax.xml.transform.Source;
045 import javax.xml.transform.TransformerException;
046 import javax.xml.transform.dom.DOMSource;
047 import javax.xml.transform.stream.StreamResult;
048
049 import org.apache.servicemix.jbi.jaxp.SourceTransformer;
050 import org.apache.servicemix.jbi.jaxp.W3CDOMStreamReader;
051 import org.apache.servicemix.jbi.jaxp.XMLStreamHelper;
052 import org.apache.servicemix.jbi.util.ByteArrayDataSource;
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);
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 }