View Javadoc

1   /*
2    * Copyright 2005-2012 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.ws.soap.axiom;
18  
19  import java.io.IOException;
20  import java.io.OutputStream;
21  import java.io.OutputStreamWriter;
22  import java.io.StringWriter;
23  import java.io.UnsupportedEncodingException;
24  import java.io.Writer;
25  import java.util.Iterator;
26  import javax.activation.DataHandler;
27  import javax.xml.stream.XMLStreamException;
28  
29  import org.apache.axiom.attachments.Attachments;
30  import org.apache.axiom.om.OMElement;
31  import org.apache.axiom.om.OMException;
32  import org.apache.axiom.om.OMOutputFormat;
33  import org.apache.axiom.om.impl.MTOMConstants;
34  import org.apache.axiom.om.impl.OMMultipartWriter;
35  import org.apache.axiom.soap.SOAPBody;
36  import org.apache.axiom.soap.SOAPEnvelope;
37  import org.apache.axiom.soap.SOAPFactory;
38  import org.apache.axiom.soap.SOAPMessage;
39  import org.apache.axiom.soap.SOAPProcessingException;
40  import org.w3c.dom.Document;
41  
42  import org.springframework.util.Assert;
43  import org.springframework.util.StringUtils;
44  import org.springframework.ws.mime.Attachment;
45  import org.springframework.ws.soap.AbstractSoapMessage;
46  import org.springframework.ws.soap.SoapEnvelope;
47  import org.springframework.ws.soap.SoapMessage;
48  import org.springframework.ws.soap.SoapVersion;
49  import org.springframework.ws.soap.axiom.support.AxiomUtils;
50  import org.springframework.ws.soap.support.SoapUtils;
51  import org.springframework.ws.stream.StreamingPayload;
52  import org.springframework.ws.stream.StreamingWebServiceMessage;
53  import org.springframework.ws.transport.TransportConstants;
54  import org.springframework.ws.transport.TransportOutputStream;
55  
56  /**
57   * AXIOM-specific implementation of the {@link SoapMessage} interface. Created via the {@link AxiomSoapMessageFactory},
58   * wraps a {@link SOAPMessage}.
59   *
60   * @author Arjen Poutsma
61   * @see SOAPMessage
62   * @since 1.0.0
63   */
64  public class AxiomSoapMessage extends AbstractSoapMessage implements StreamingWebServiceMessage {
65  
66      private static final String EMPTY_SOAP_ACTION = "\"\"";
67  
68      private SOAPMessage axiomMessage;
69  
70      private final SOAPFactory axiomFactory;
71  
72      private final Attachments attachments;
73  
74      private final boolean payloadCaching;
75  
76      private AxiomSoapEnvelope envelope;
77  
78      private String soapAction;
79  
80      private final boolean langAttributeOnSoap11FaultString;
81  
82      private OMOutputFormat outputFormat;
83  
84      /**
85       * Create a new, empty <code>AxiomSoapMessage</code>.
86       *
87       * @param soapFactory the AXIOM SOAPFactory
88       */
89      public AxiomSoapMessage(SOAPFactory soapFactory) {
90          this(soapFactory, true, true);
91      }
92  
93      /**
94       * Create a new, empty <code>AxiomSoapMessage</code>.
95       *
96       * @param soapFactory the AXIOM SOAPFactory
97       */
98      public AxiomSoapMessage(SOAPFactory soapFactory, boolean payloadCaching, boolean langAttributeOnSoap11FaultString) {
99          SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();
100         axiomFactory = soapFactory;
101 	    axiomMessage = axiomFactory.createSOAPMessage();
102 	    axiomMessage.setSOAPEnvelope(soapEnvelope);
103         attachments = new Attachments();
104         this.payloadCaching = payloadCaching;
105         this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString;
106         soapAction = EMPTY_SOAP_ACTION;
107     }
108 
109     /**
110      * Create a new <code>AxiomSoapMessage</code> based on the given AXIOM <code>SOAPMessage</code>.
111      *
112      * @param soapMessage    the AXIOM SOAPMessage
113      * @param soapAction     the value of the SOAP Action header
114      * @param payloadCaching whether the contents of the SOAP body should be cached or not
115      */
116     public AxiomSoapMessage(SOAPMessage soapMessage,
117                             String soapAction,
118                             boolean payloadCaching,
119                             boolean langAttributeOnSoap11FaultString) {
120         this(soapMessage, new Attachments(), soapAction, payloadCaching, langAttributeOnSoap11FaultString);
121     }
122 
123     /**
124      * Create a new <code>AxiomSoapMessage</code> based on the given AXIOM <code>SOAPMessage</code> and attachments.
125      *
126      * @param soapMessage    the AXIOM SOAPMessage
127      * @param attachments    the attachments
128      * @param soapAction     the value of the SOAP Action header
129      * @param payloadCaching whether the contents of the SOAP body should be cached or not
130      */
131     public AxiomSoapMessage(SOAPMessage soapMessage,
132                             Attachments attachments,
133                             String soapAction,
134                             boolean payloadCaching,
135                             boolean langAttributeOnSoap11FaultString) {
136         Assert.notNull(soapMessage, "'soapMessage' must not be null");
137         Assert.notNull(attachments, "'attachments' must not be null");
138         axiomMessage = soapMessage;
139         axiomFactory = (SOAPFactory) soapMessage.getSOAPEnvelope().getOMFactory();
140         this.attachments = attachments;
141         if (!StringUtils.hasLength(soapAction)) {
142             soapAction = EMPTY_SOAP_ACTION;
143         }
144         this.soapAction = soapAction;
145         this.payloadCaching = payloadCaching;
146         this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString;
147     }
148 
149     /** Return the AXIOM <code>SOAPMessage</code> that this <code>AxiomSoapMessage</code> is based on. */
150     public final SOAPMessage getAxiomMessage() {
151         return axiomMessage;
152     }
153 
154     /**
155      * Sets the AXIOM <code>SOAPMessage</code> that this <code>AxiomSoapMessage</code> is based on.
156      * <p/>
157      * Calling this method also clears the SOAP Action property.
158      */
159     public final void setAxiomMessage(SOAPMessage axiomMessage) {
160         Assert.notNull(axiomMessage, "'axiomMessage' must not be null");
161         this.axiomMessage = axiomMessage;
162         this.envelope = null;
163         this.soapAction = EMPTY_SOAP_ACTION;
164     }
165 
166     /**
167      * Sets the {@link OMOutputFormat} to be used when writing the message.
168      *
169      * @see #writeTo(java.io.OutputStream)
170      */
171     public void setOutputFormat(OMOutputFormat outputFormat) {
172         this.outputFormat = outputFormat;
173     }
174 
175     public void setStreamingPayload(StreamingPayload payload) {
176         AxiomSoapBody soapBody = (AxiomSoapBody) getSoapBody();
177         soapBody.setStreamingPayload(payload);
178     }
179 
180     public SoapEnvelope getEnvelope() {
181         if (envelope == null) {
182             try {
183                 envelope = new AxiomSoapEnvelope(axiomMessage.getSOAPEnvelope(), axiomFactory, payloadCaching,
184                         langAttributeOnSoap11FaultString);
185             }
186             catch (SOAPProcessingException ex) {
187                 throw new AxiomSoapEnvelopeException(ex);
188             }
189         }
190         return envelope;
191     }
192 
193     public String getSoapAction() {
194         return soapAction;
195     }
196 
197     public void setSoapAction(String soapAction) {
198         soapAction = SoapUtils.escapeAction(soapAction);
199         this.soapAction = soapAction;
200     }
201 
202     public Document getDocument() {
203         return AxiomUtils.toDocument(axiomMessage.getSOAPEnvelope());
204     }
205 
206     public void setDocument(Document document) {
207         // save the Soap Action
208         String soapAction = getSoapAction();
209         SOAPEnvelope envelope = AxiomUtils.toEnvelope(document);
210         SOAPMessage newMessage = axiomFactory.createSOAPMessage();
211         newMessage.setSOAPEnvelope(envelope);
212 
213         // replace the Axiom message
214         setAxiomMessage(newMessage);
215         // restore the Soap Action
216         setSoapAction(soapAction);
217     }
218 
219     public boolean isXopPackage() {
220         try {
221             return MTOMConstants.MTOM_TYPE.equals(attachments.getAttachmentSpecType());
222         }
223         catch (OMException ex) {
224             return false;
225         }
226         catch (NullPointerException ex) {
227             // gotta love Axis2
228             return false;
229         }
230     }
231 
232     public boolean convertToXopPackage() {
233         return false;
234     }
235 
236     public Attachment getAttachment(String contentId) {
237         Assert.hasLength(contentId, "contentId must not be empty");
238         if (contentId.startsWith("<") && contentId.endsWith(">")) {
239             contentId = contentId.substring(1, contentId.length() - 1);
240         }
241         DataHandler dataHandler = attachments.getDataHandler(contentId);
242         return dataHandler != null ? new AxiomAttachment(contentId, dataHandler) : null;
243     }
244 
245     public Iterator<Attachment> getAttachments() {
246         return new AxiomAttachmentIterator();
247     }
248 
249     public Attachment addAttachment(String contentId, DataHandler dataHandler) {
250         Assert.hasLength(contentId, "contentId must not be empty");
251         Assert.notNull(dataHandler, "dataHandler must not be null");
252         attachments.addDataHandler(contentId, dataHandler);
253         return new AxiomAttachment(contentId, dataHandler);
254     }
255 
256     public void writeTo(OutputStream outputStream) throws IOException {
257         try {
258 
259             OMOutputFormat outputFormat = getOutputFormat();
260             if (outputStream instanceof TransportOutputStream) {
261                 TransportOutputStream transportOutputStream = (TransportOutputStream) outputStream;
262                 String contentType = outputFormat.getContentType();
263                 if (!(outputFormat.isDoingSWA() || outputFormat.isOptimized())) {
264                     String charsetEncoding = axiomMessage.getCharsetEncoding();
265                     contentType += "; charset=" + charsetEncoding;
266                 }
267                 SoapVersion version = getVersion();
268                 if (SoapVersion.SOAP_11 == version) {
269                     transportOutputStream.addHeader(TransportConstants.HEADER_SOAP_ACTION, soapAction);
270                     transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType());
271                 }
272                 else if (SoapVersion.SOAP_12 == version) {
273                     contentType += "; action=" + soapAction;
274                     transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType());
275                 }
276                 transportOutputStream.addHeader(TransportConstants.HEADER_CONTENT_TYPE, contentType);
277 
278             }
279             if (!(outputFormat.isOptimized()) & outputFormat.isDoingSWA()) {
280                 writeSwAMessage(outputStream, outputFormat);
281             }
282             else {
283                 if (payloadCaching) {
284                     axiomMessage.serialize(outputStream, outputFormat);
285                 }
286                 else {
287                     axiomMessage.serializeAndConsume(outputStream, outputFormat);
288                 }
289             }
290             outputStream.flush();
291         }
292         catch (XMLStreamException ex) {
293             throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex);
294         }
295         catch (OMException ex) {
296             throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex);
297         }
298     }
299 
300     private OMOutputFormat getOutputFormat() {
301         if (outputFormat != null) {
302             return outputFormat;
303         }
304         else {
305             String charsetEncoding = axiomMessage.getCharsetEncoding();
306 
307             OMOutputFormat outputFormat = new OMOutputFormat();
308             outputFormat.setCharSetEncoding(charsetEncoding);
309             outputFormat.setSOAP11(getVersion() == SoapVersion.SOAP_11);
310             if (isXopPackage()) {
311                 outputFormat.setDoOptimize(true);
312             }
313             else if (!attachments.getContentIDSet().isEmpty()) {
314                 outputFormat.setDoingSWA(true);
315             }
316             return outputFormat;
317         }
318     }
319 
320     private void writeSwAMessage(OutputStream outputStream, OMOutputFormat format)
321             throws XMLStreamException, UnsupportedEncodingException {
322         StringWriter writer = new StringWriter();
323         SOAPEnvelope envelope = axiomMessage.getSOAPEnvelope();
324         if (payloadCaching) {
325             envelope.serialize(writer, format);
326         }
327         else {
328             envelope.serializeAndConsume(writer, format);
329         }
330 
331 	    try {
332 		    OMMultipartWriter mpw = new OMMultipartWriter(outputStream, format);
333 
334 		    Writer rootPartWriter = new OutputStreamWriter(mpw.writeRootPart(),
335 				    format.getCharSetEncoding());
336 		    rootPartWriter.write(writer.toString());
337 		    rootPartWriter.close();
338 
339 		    // Get the collection of ids associated with the attachments
340 		    for (String id: attachments.getAllContentIDs()) {
341 			    mpw.writePart(attachments.getDataHandler(id), id);
342 		    }
343 
344 		    mpw.complete();
345 	    }
346 	    catch (IOException ex) {
347 		    throw new OMException("Error writing SwA message", ex);
348 	    }
349     }
350 
351     public String toString() {
352         StringBuilder builder = new StringBuilder("AxiomSoapMessage");
353         if (payloadCaching) {
354             try {
355                 SOAPEnvelope envelope = axiomMessage.getSOAPEnvelope();
356                 if (envelope != null) {
357                     SOAPBody body = envelope.getBody();
358                     if (body != null) {
359                         OMElement bodyElement = body.getFirstElement();
360                         if (bodyElement != null) {
361                             builder.append(' ');
362                             builder.append(bodyElement.getQName());
363                         }
364                     }
365                 }
366             }
367             catch (OMException ex) {
368                 // ignore
369             }
370         }
371         return builder.toString();
372     }
373 
374     private class AxiomAttachmentIterator implements Iterator<Attachment> {
375 
376         private final Iterator<String> iterator;
377 
378         @SuppressWarnings("unchecked")
379         private AxiomAttachmentIterator() {
380             iterator = attachments.getContentIDSet().iterator();
381         }
382 
383         public boolean hasNext() {
384             return iterator.hasNext();
385         }
386 
387         public Attachment next() {
388             String contentId = iterator.next();
389             DataHandler dataHandler = attachments.getDataHandler(contentId);
390             return new AxiomAttachment(contentId, dataHandler);
391         }
392 
393         public void remove() {
394             iterator.remove();
395         }
396     }
397 
398 }