View Javadoc

1   /*
2    * Copyright 2002-2009 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.StringWriter;
22  import java.io.UnsupportedEncodingException;
23  import java.util.Iterator;
24  import javax.activation.DataHandler;
25  import javax.xml.stream.XMLStreamException;
26  
27  import org.apache.axiom.attachments.Attachments;
28  import org.apache.axiom.om.OMElement;
29  import org.apache.axiom.om.OMException;
30  import org.apache.axiom.om.OMOutputFormat;
31  import org.apache.axiom.om.impl.MIMEOutputUtils;
32  import org.apache.axiom.om.impl.MTOMConstants;
33  import org.apache.axiom.soap.SOAPBody;
34  import org.apache.axiom.soap.SOAPEnvelope;
35  import org.apache.axiom.soap.SOAPFactory;
36  import org.apache.axiom.soap.SOAPMessage;
37  import org.apache.axiom.soap.SOAPProcessingException;
38  
39  import org.springframework.util.Assert;
40  import org.springframework.util.StringUtils;
41  import org.springframework.ws.mime.Attachment;
42  import org.springframework.ws.soap.AbstractSoapMessage;
43  import org.springframework.ws.soap.SoapEnvelope;
44  import org.springframework.ws.soap.SoapMessage;
45  import org.springframework.ws.soap.SoapVersion;
46  import org.springframework.ws.soap.support.SoapUtils;
47  import org.springframework.ws.transport.TransportConstants;
48  import org.springframework.ws.transport.TransportOutputStream;
49  
50  /**
51   * AXIOM-specific implementation of the {@link SoapMessage} interface. Created via the {@link AxiomSoapMessageFactory},
52   * wraps a {@link SOAPMessage}.
53   *
54   * @author Arjen Poutsma
55   * @see SOAPMessage
56   * @since 1.0.0
57   */
58  public class AxiomSoapMessage extends AbstractSoapMessage {
59  
60      private static final String EMPTY_SOAP_ACTION = "\"\"";
61  
62      private SOAPMessage axiomMessage;
63  
64      private final SOAPFactory axiomFactory;
65  
66      private final Attachments attachments;
67  
68      private final boolean payloadCaching;
69  
70      private AxiomSoapEnvelope envelope;
71  
72      private String soapAction;
73  
74      private final boolean langAttributeOnSoap11FaultString;
75  
76      private OMOutputFormat outputFormat;
77  
78      /**
79       * Create a new, empty <code>AxiomSoapMessage</code>.
80       *
81       * @param soapFactory the AXIOM SOAPFactory
82       */
83      public AxiomSoapMessage(SOAPFactory soapFactory) {
84          this(soapFactory, true, true);
85      }
86  
87      /**
88       * Create a new, empty <code>AxiomSoapMessage</code>.
89       *
90       * @param soapFactory the AXIOM SOAPFactory
91       */
92      public AxiomSoapMessage(SOAPFactory soapFactory, boolean payloadCaching, boolean langAttributeOnSoap11FaultString) {
93          SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();
94          axiomFactory = soapFactory;
95          axiomMessage = axiomFactory.createSOAPMessage(soapEnvelope, soapEnvelope.getBuilder());
96          attachments = new Attachments();
97          this.payloadCaching = payloadCaching;
98          this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString;
99          soapAction = EMPTY_SOAP_ACTION;
100     }
101 
102     /**
103      * Create a new <code>AxiomSoapMessage</code> based on the given AXIOM <code>SOAPMessage</code>.
104      *
105      * @param soapMessage    the AXIOM SOAPMessage
106      * @param soapAction     the value of the SOAP Action header
107      * @param payloadCaching whether the contents of the SOAP body should be cached or not
108      */
109     public AxiomSoapMessage(SOAPMessage soapMessage,
110                             String soapAction,
111                             boolean payloadCaching,
112                             boolean langAttributeOnSoap11FaultString) {
113         this(soapMessage, new Attachments(), soapAction, payloadCaching, langAttributeOnSoap11FaultString);
114     }
115 
116     /**
117      * Create a new <code>AxiomSoapMessage</code> based on the given AXIOM <code>SOAPMessage</code> and attachments.
118      *
119      * @param soapMessage    the AXIOM SOAPMessage
120      * @param attachments    the attachments
121      * @param soapAction     the value of the SOAP Action header
122      * @param payloadCaching whether the contents of the SOAP body should be cached or not
123      */
124     public AxiomSoapMessage(SOAPMessage soapMessage,
125                             Attachments attachments,
126                             String soapAction,
127                             boolean payloadCaching,
128                             boolean langAttributeOnSoap11FaultString) {
129         Assert.notNull(soapMessage, "'soapMessage' must not be null");
130         Assert.notNull(attachments, "'attachments' must not be null");
131         axiomMessage = soapMessage;
132         axiomFactory = (SOAPFactory) soapMessage.getSOAPEnvelope().getOMFactory();
133         this.attachments = attachments;
134         if (!StringUtils.hasLength(soapAction)) {
135             soapAction = EMPTY_SOAP_ACTION;
136         }
137         this.soapAction = soapAction;
138         this.payloadCaching = payloadCaching;
139         this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString;
140     }
141 
142     /** Return the AXIOM <code>SOAPMessage</code> that this <code>AxiomSoapMessage</code> is based on. */
143     public final SOAPMessage getAxiomMessage() {
144         return axiomMessage;
145     }
146 
147     /**
148      * Sets the AXIOM <code>SOAPMessage</code> that this <code>AxiomSoapMessage</code> is based on.
149      * <p/>
150      * Calling this method also clears the SOAP Action property.
151      */
152     public final void setAxiomMessage(SOAPMessage axiomMessage) {
153         Assert.notNull(axiomMessage, "'axiomMessage' must not be null");
154         this.axiomMessage = axiomMessage;
155         this.envelope = null;
156         this.soapAction = EMPTY_SOAP_ACTION;
157     }
158 
159     /**
160      * Sets the {@link OMOutputFormat} to be used when writing the message.
161      *
162      * @see #writeTo(java.io.OutputStream)
163      */
164     public void setOutputFormat(OMOutputFormat outputFormat) {
165         this.outputFormat = outputFormat;
166     }
167 
168     public SoapEnvelope getEnvelope() {
169         if (envelope == null) {
170             try {
171                 envelope = new AxiomSoapEnvelope(axiomMessage.getSOAPEnvelope(), axiomFactory, payloadCaching,
172                         langAttributeOnSoap11FaultString);
173             }
174             catch (SOAPProcessingException ex) {
175                 throw new AxiomSoapEnvelopeException(ex);
176             }
177         }
178         return envelope;
179     }
180 
181     public String getSoapAction() {
182         return soapAction;
183     }
184 
185     public void setSoapAction(String soapAction) {
186         soapAction = SoapUtils.escapeAction(soapAction);
187         this.soapAction = soapAction;
188     }
189 
190     public boolean isXopPackage() {
191         try {
192             return MTOMConstants.MTOM_TYPE.equals(attachments.getAttachmentSpecType());
193         }
194         catch (NullPointerException ex) {
195             // gotta love Axis2
196             return false;
197         }
198     }
199 
200     public boolean convertToXopPackage() {
201         return false;
202     }
203 
204     public Attachment getAttachment(String contentId) {
205         Assert.hasLength(contentId, "contentId must not be empty");
206         if (contentId.startsWith("<") && contentId.endsWith(">")) {
207             contentId = contentId.substring(1, contentId.length() - 1);
208         }
209         DataHandler dataHandler = attachments.getDataHandler(contentId);
210         return dataHandler != null ? new AxiomAttachment(contentId, dataHandler) : null;
211     }
212 
213     public Iterator getAttachments() {
214         return new AxiomAttachmentIterator();
215     }
216 
217     public Attachment addAttachment(String contentId, DataHandler dataHandler) {
218         Assert.hasLength(contentId, "contentId must not be empty");
219         Assert.notNull(dataHandler, "dataHandler must not be null");
220         attachments.addDataHandler(contentId, dataHandler);
221         return new AxiomAttachment(contentId, dataHandler);
222     }
223 
224     public void writeTo(OutputStream outputStream) throws IOException {
225         try {
226 
227             OMOutputFormat outputFormat = getOutputFormat();
228             if (outputStream instanceof TransportOutputStream) {
229                 TransportOutputStream transportOutputStream = (TransportOutputStream) outputStream;
230                 String contentType = outputFormat.getContentType();
231                 if (!(outputFormat.isDoingSWA() || outputFormat.isOptimized())) {
232                     String charsetEncoding = axiomMessage.getCharsetEncoding();
233                     contentType += "; charset=" + charsetEncoding;
234                 }
235                 SoapVersion version = getVersion();
236                 if (SoapVersion.SOAP_11 == version) {
237                     transportOutputStream.addHeader(TransportConstants.HEADER_SOAP_ACTION, soapAction);
238                     transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType());
239                 }
240                 else if (SoapVersion.SOAP_12 == version) {
241                     contentType += "; action=" + soapAction;
242                     transportOutputStream.addHeader(TransportConstants.HEADER_ACCEPT, version.getContentType());
243                 }
244                 transportOutputStream.addHeader(TransportConstants.HEADER_CONTENT_TYPE, contentType);
245 
246             }
247             if (!(outputFormat.isOptimized()) & outputFormat.isDoingSWA()) {
248                 writeSwAMessage(outputStream, outputFormat);
249             }
250             else {
251                 if (payloadCaching) {
252                     axiomMessage.serialize(outputStream, outputFormat);
253                 }
254                 else {
255                     axiomMessage.serializeAndConsume(outputStream, outputFormat);
256                 }
257             }
258             outputStream.flush();
259         }
260         catch (XMLStreamException ex) {
261             throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex);
262         }
263         catch (OMException ex) {
264             throw new AxiomSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex);
265         }
266     }
267 
268     private OMOutputFormat getOutputFormat() {
269         if (outputFormat != null) {
270             return outputFormat;
271         }
272         else {
273             String charsetEncoding = axiomMessage.getCharsetEncoding();
274 
275             OMOutputFormat outputFormat = new OMOutputFormat();
276             outputFormat.setCharSetEncoding(charsetEncoding);
277             outputFormat.setSOAP11(getVersion() == SoapVersion.SOAP_11);
278             if (isXopPackage()) {
279                 outputFormat.setDoOptimize(true);
280             }
281             else if (!attachments.getContentIDSet().isEmpty()) {
282                 outputFormat.setDoingSWA(true);
283             }
284             return outputFormat;
285         }
286     }
287 
288     private void writeSwAMessage(OutputStream outputStream, OMOutputFormat format)
289             throws XMLStreamException, UnsupportedEncodingException {
290         StringWriter writer = new StringWriter();
291         SOAPEnvelope envelope = axiomMessage.getSOAPEnvelope();
292         if (payloadCaching) {
293             envelope.serialize(writer, format);
294         }
295         else {
296             envelope.serializeAndConsume(writer, format);
297         }
298         MIMEOutputUtils.writeSOAPWithAttachmentsMessage(writer, outputStream, attachments, format);
299     }
300 
301     public String toString() {
302         StringBuffer buffer = new StringBuffer("AxiomSoapMessage");
303         if (payloadCaching) {
304             try {
305                 SOAPEnvelope envelope = axiomMessage.getSOAPEnvelope();
306                 if (envelope != null) {
307                     SOAPBody body = envelope.getBody();
308                     if (body != null) {
309                         OMElement bodyElement = body.getFirstElement();
310                         if (bodyElement != null) {
311                             buffer.append(' ');
312                             buffer.append(bodyElement.getQName());
313                         }
314                     }
315                 }
316             }
317             catch (OMException ex) {
318                 // ignore
319             }
320         }
321         return buffer.toString();
322     }
323 
324     private class AxiomAttachmentIterator implements Iterator {
325 
326         private final Iterator iterator;
327 
328         private AxiomAttachmentIterator() {
329             iterator = attachments.getContentIDSet().iterator();
330         }
331 
332         public boolean hasNext() {
333             return iterator.hasNext();
334         }
335 
336         public Object next() {
337             String contentId = (String) iterator.next();
338             DataHandler dataHandler = attachments.getDataHandler(contentId);
339             return new AxiomAttachment(contentId, dataHandler);
340         }
341 
342         public void remove() {
343             iterator.remove();
344         }
345     }
346 
347 }