View Javadoc

1   /*
2    * Copyright 2005-2010 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.saaj;
18  
19  import java.io.ByteArrayInputStream;
20  import java.io.ByteArrayOutputStream;
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.util.Iterator;
24  import javax.activation.DataHandler;
25  import javax.xml.soap.AttachmentPart;
26  import javax.xml.soap.MessageFactory;
27  import javax.xml.soap.MimeHeaders;
28  import javax.xml.soap.SOAPBody;
29  import javax.xml.soap.SOAPElement;
30  import javax.xml.soap.SOAPEnvelope;
31  import javax.xml.soap.SOAPException;
32  import javax.xml.soap.SOAPMessage;
33  import javax.xml.soap.SOAPPart;
34  
35  import org.springframework.util.Assert;
36  import org.springframework.util.ObjectUtils;
37  import org.springframework.ws.mime.Attachment;
38  import org.springframework.ws.mime.AttachmentException;
39  import org.springframework.ws.soap.AbstractSoapMessage;
40  import org.springframework.ws.soap.SoapEnvelope;
41  import org.springframework.ws.soap.SoapMessage;
42  import org.springframework.ws.soap.SoapVersion;
43  import org.springframework.ws.soap.saaj.support.SaajUtils;
44  import org.springframework.ws.soap.support.SoapUtils;
45  import org.springframework.ws.transport.TransportConstants;
46  
47  import org.w3c.dom.DOMImplementation;
48  import org.w3c.dom.Document;
49  import org.w3c.dom.ls.DOMImplementationLS;
50  import org.w3c.dom.ls.LSOutput;
51  import org.w3c.dom.ls.LSSerializer;
52  
53  /**
54   * SAAJ-specific implementation of the {@link SoapMessage} interface. Created via the {@link SaajSoapMessageFactory},
55   * wraps a {@link SOAPMessage}.
56   *
57   * @author Arjen Poutsma
58   * @see SOAPMessage
59   * @since 1.0.0
60   */
61  public class SaajSoapMessage extends AbstractSoapMessage {
62  
63      private static final String CONTENT_TYPE_XOP = "application/xop+xml";
64  
65      private final MessageFactory messageFactory;
66  
67      private SOAPMessage saajMessage;
68  
69      private SoapEnvelope envelope;
70  
71      private final boolean langAttributeOnSoap11FaultString;
72  
73      private SaajImplementation implementation;
74  
75      /**
76       * Create a new <code>SaajSoapMessage</code> based on the given SAAJ <code>SOAPMessage</code>.
77       *
78       * @param soapMessage the SAAJ SOAPMessage
79       */
80      public SaajSoapMessage(SOAPMessage soapMessage) {
81          this(soapMessage, true, null);
82      }
83  
84      /**
85       * Create a new <code>SaajSoapMessage</code> based on the given SAAJ <code>SOAPMessage</code>.
86       *
87       * @param soapMessage the SAAJ SOAPMessage
88       * @param messageFactory the SAAJ message factory
89       */
90      public SaajSoapMessage(SOAPMessage soapMessage, MessageFactory messageFactory) {
91          this(soapMessage, true, messageFactory);
92      }
93  
94      /**
95       * Create a new <code>SaajSoapMessage</code> based on the given SAAJ <code>SOAPMessage</code>.
96       *
97       * @param soapMessage the SAAJ SOAPMessage
98       * @param langAttributeOnSoap11FaultString
99       *                    whether a {@code xml:lang} attribute is allowed on SOAP 1.1 {@code <faultstring>} elements
100      */
101     public SaajSoapMessage(SOAPMessage soapMessage, boolean langAttributeOnSoap11FaultString) {
102         this(soapMessage, langAttributeOnSoap11FaultString, null);
103     }
104 
105     /**
106      * Create a new <code>SaajSoapMessage</code> based on the given SAAJ <code>SOAPMessage</code>.
107      *
108      * @param soapMessage the SAAJ SOAPMessage
109      * @param langAttributeOnSoap11FaultString
110      *                    whether a {@code xml:lang} attribute is allowed on SOAP 1.1 {@code <faultstring>} elements
111      * @param messageFactory the message factory
112      */
113     public SaajSoapMessage(SOAPMessage soapMessage, boolean langAttributeOnSoap11FaultString, MessageFactory messageFactory) {
114         Assert.notNull(soapMessage, "soapMessage must not be null");
115         saajMessage = soapMessage;
116         this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString;
117         MimeHeaders headers = getImplementation().getMimeHeaders(soapMessage);
118         if (ObjectUtils.isEmpty(headers.getHeader(TransportConstants.HEADER_SOAP_ACTION))) {
119             headers.addHeader(TransportConstants.HEADER_SOAP_ACTION, "\"\"");
120         }
121         this.messageFactory = messageFactory;
122     }
123 
124     /** Return the SAAJ <code>SOAPMessage</code> that this <code>SaajSoapMessage</code> is based on. */
125     public SOAPMessage getSaajMessage() {
126         return saajMessage;
127     }
128 
129     /** Sets the SAAJ <code>SOAPMessage</code> that this <code>SaajSoapMessage</code> is based on. */
130     public void setSaajMessage(SOAPMessage soapMessage) {
131         Assert.notNull(soapMessage, "soapMessage must not be null");
132         saajMessage = soapMessage;
133         envelope = null;
134     }
135 
136     public SoapEnvelope getEnvelope() {
137         if (envelope == null) {
138             try {
139                 SOAPEnvelope saajEnvelope = getImplementation().getEnvelope(getSaajMessage());
140                 envelope = new SaajSoapEnvelope(saajEnvelope, langAttributeOnSoap11FaultString);
141             }
142             catch (SOAPException ex) {
143                 throw new SaajSoapEnvelopeException(ex);
144             }
145         }
146         return envelope;
147     }
148 
149     public String getSoapAction() {
150         MimeHeaders mimeHeaders = getImplementation().getMimeHeaders(getSaajMessage());
151         if (SoapVersion.SOAP_11 == getVersion()) {
152             String[] actions = mimeHeaders.getHeader(TransportConstants.HEADER_SOAP_ACTION);
153             return ObjectUtils.isEmpty(actions) ? TransportConstants.EMPTY_SOAP_ACTION : actions[0];
154         }
155         else if (SoapVersion.SOAP_12 == getVersion()) {
156             String[] contentTypes = mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE);
157             return !ObjectUtils.isEmpty(contentTypes) ? SoapUtils.extractActionFromContentType(contentTypes[0]) :
158                     TransportConstants.EMPTY_SOAP_ACTION;
159         }
160         else {
161             throw new IllegalStateException("Unsupported SOAP version: " + getVersion());
162         }
163     }
164 
165     public void setSoapAction(String soapAction) {
166         MimeHeaders mimeHeaders = getImplementation().getMimeHeaders(getSaajMessage());
167         soapAction = SoapUtils.escapeAction(soapAction);
168         if (SoapVersion.SOAP_11 == getVersion()) {
169             mimeHeaders.setHeader(TransportConstants.HEADER_SOAP_ACTION, soapAction);
170         }
171         else if (SoapVersion.SOAP_12 == getVersion()) {
172             // force save of Content Type header
173             if (saajMessage.saveRequired()) {
174                 try {
175                     saajMessage.saveChanges();
176                 }
177                 catch (SOAPException ex) {
178                     throw new SaajSoapMessageException("Could not save message", ex);
179                 }
180             }
181             String[] contentTypes = mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE);
182             String contentType = !ObjectUtils.isEmpty(contentTypes) ? contentTypes[0] : getVersion().getContentType();
183             contentType = SoapUtils.setActionInContentType(contentType, soapAction);
184             mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_TYPE, contentType);
185             mimeHeaders.removeHeader(TransportConstants.HEADER_SOAP_ACTION);
186         }
187         else {
188             throw new IllegalStateException("Unsupported SOAP version: " + getVersion());
189         }
190 
191     }
192 
193     public Document getDocument() {
194         Assert.state(messageFactory != null, "Could find message factory to use");
195         // return saajSoapMessage.getSaajMessage().getSOAPPart(); // does not work, see SWS-345
196         try {
197             ByteArrayOutputStream bos = new ByteArrayOutputStream();
198             getSaajMessage().writeTo(bos);
199             ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
200             SOAPMessage saajMessage = messageFactory.createMessage(getSaajMessage().getMimeHeaders(), bis);
201             setSaajMessage(saajMessage);
202             return saajMessage.getSOAPPart();
203         }
204         catch (SOAPException ex) {
205             throw new SaajSoapMessageException("Could not save changes", ex);
206         }
207         catch (IOException ex) {
208             throw new SaajSoapMessageException("Could not save changes", ex);
209         }
210     }
211 
212     public void setDocument(Document document) {
213         if (saajMessage.getSOAPPart() != document) {
214             Assert.state(messageFactory != null, "Could find message factory to use");
215             try {
216                 DOMImplementation implementation = document.getImplementation();
217                 Assert.isInstanceOf(DOMImplementationLS.class, implementation);
218 
219                 DOMImplementationLS loadSaveImplementation = (DOMImplementationLS) implementation;
220                 LSOutput output = loadSaveImplementation.createLSOutput();
221                 ByteArrayOutputStream bos = new ByteArrayOutputStream();
222                 output.setByteStream(bos);
223 
224                 LSSerializer serializer = loadSaveImplementation.createLSSerializer();
225                 serializer.write(document, output);
226 
227                 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
228 
229                 this.saajMessage = messageFactory.createMessage(saajMessage.getMimeHeaders(), bis);
230 
231             }
232             catch (SOAPException ex) {
233                 throw new SaajSoapMessageException("Could not read input stream", ex);
234             }
235             catch (IOException ex) {
236                 throw new SaajSoapMessageException("Could not read input stream", ex);
237             }
238         }
239     }
240 
241     public void writeTo(OutputStream outputStream) throws IOException {
242         MimeHeaders mimeHeaders = getImplementation().getMimeHeaders(getSaajMessage());
243         if (ObjectUtils.isEmpty(mimeHeaders.getHeader(TransportConstants.HEADER_ACCEPT))) {
244             mimeHeaders.setHeader(TransportConstants.HEADER_ACCEPT, getVersion().getContentType());
245         }
246         try {
247             getImplementation().writeTo(getSaajMessage(), outputStream);
248             outputStream.flush();
249         }
250         catch (SOAPException ex) {
251             throw new SaajSoapMessageException("Could not write message to OutputStream: " + ex.getMessage(), ex);
252         }
253     }
254 
255     private int getSaajVersion() {
256         try {
257             return SaajUtils.getSaajVersion(saajMessage);
258         }
259         catch (SOAPException ex) {
260             throw new SaajSoapEnvelopeException("Could not access envelope: " + ex.getMessage(), ex);
261         }
262     }
263 
264     public boolean isXopPackage() {
265         if (getSaajVersion() >= SaajUtils.SAAJ_13) {
266             SOAPPart saajPart = saajMessage.getSOAPPart();
267             String[] contentTypes = saajPart.getMimeHeader(TransportConstants.HEADER_CONTENT_TYPE);
268             for (String contentType : contentTypes) {
269                 if (contentType.indexOf(CONTENT_TYPE_XOP) != -1) {
270                     return true;
271                 }
272             }
273         }
274         return false;
275     }
276 
277     public boolean convertToXopPackage() {
278         if (getSaajVersion() >= SaajUtils.SAAJ_13) {
279             convertMessageToXop();
280             convertPartToXop();
281             return true;
282         }
283         else {
284             return false;
285         }
286     }
287 
288     private void convertMessageToXop() {
289         MimeHeaders mimeHeaders = saajMessage.getMimeHeaders();
290         String[] oldContentTypes = mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE);
291         String oldContentType =
292                 !ObjectUtils.isEmpty(oldContentTypes) ? oldContentTypes[0] : getVersion().getContentType();
293         StringBuilder builder = new StringBuilder(CONTENT_TYPE_XOP);
294         builder.append(";type=");
295         builder.append('"');
296         builder.append(oldContentType);
297         builder.append('"');
298         mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_TYPE, builder.toString());
299     }
300 
301     private void convertPartToXop() {
302         SOAPPart saajPart = saajMessage.getSOAPPart();
303         String[] oldContentTypes = saajPart.getMimeHeader(TransportConstants.HEADER_CONTENT_TYPE);
304         String oldContentType =
305                 !ObjectUtils.isEmpty(oldContentTypes) ? oldContentTypes[0] : getVersion().getContentType();
306         StringBuilder builder = new StringBuilder(CONTENT_TYPE_XOP);
307         builder.append(";type=");
308         builder.append('"');
309         builder.append(oldContentType);
310         builder.append('"');
311         saajPart.setMimeHeader(TransportConstants.HEADER_CONTENT_TYPE, builder.toString());
312     }
313 
314     public Iterator<Attachment> getAttachments() throws AttachmentException {
315         Iterator<AttachmentPart> iterator = getImplementation().getAttachments(getSaajMessage());
316         return new SaajAttachmentIterator(iterator);
317     }
318 
319     public Attachment getAttachment(String contentId) {
320         Assert.hasLength(contentId, "contentId must not be empty");
321         MimeHeaders mimeHeaders = new MimeHeaders();
322         mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_ID, contentId);
323         Iterator<AttachmentPart> iterator = getImplementation().getAttachment(getSaajMessage(), mimeHeaders);
324         if (!iterator.hasNext()) {
325             return null;
326         }
327         AttachmentPart saajAttachment = iterator.next();
328         return new SaajAttachment(saajAttachment);
329     }
330 
331     public Attachment addAttachment(String contentId, DataHandler dataHandler) {
332         Assert.hasLength(contentId, "contentId must not be empty");
333         Assert.notNull(dataHandler, "dataHandler must not be null");
334         AttachmentPart saajAttachment = getImplementation().addAttachmentPart(getSaajMessage(), dataHandler);
335         saajAttachment.setContentId(contentId);
336         saajAttachment.setMimeHeader(TransportConstants.HEADER_CONTENT_TRANSFER_ENCODING, "binary");
337         return new SaajAttachment(saajAttachment);
338     }
339 
340     protected final SaajImplementation getImplementation() {
341         if (implementation == null) {
342             int saajVersion = getSaajVersion();
343             if (saajVersion == SaajUtils.SAAJ_13) {
344                 implementation = Saaj13Implementation.getInstance();
345             }
346             else if (saajVersion == SaajUtils.SAAJ_12) {
347                 implementation = Saaj12Implementation.getInstance();
348             }
349             else if (saajVersion == SaajUtils.SAAJ_11) {
350                 implementation = Saaj11Implementation.getInstance();
351             }
352             else {
353                 throw new IllegalStateException("Could not find SAAJ on the classpath");
354             }
355         }
356         return implementation;
357     }
358 
359     public String toString() {
360         StringBuilder builder = new StringBuilder("SaajSoapMessage");
361         try {
362             SOAPEnvelope envelope = getImplementation().getEnvelope(saajMessage);
363             if (envelope != null) {
364                 SOAPBody body = getImplementation().getBody(envelope);
365                 if (body != null) {
366                     SOAPElement bodyElement = getImplementation().getFirstBodyElement(body);
367                     if (bodyElement != null) {
368                         builder.append(' ');
369                         builder.append(getImplementation().getName(bodyElement));
370                     }
371                 }
372             }
373         }
374         catch (SOAPException ex) {
375             // ignore
376         }
377         return builder.toString();
378     }
379 
380     private static class SaajAttachmentIterator implements Iterator<Attachment> {
381 
382         private final Iterator<AttachmentPart> saajIterator;
383 
384         private SaajAttachmentIterator(Iterator<AttachmentPart> saajIterator) {
385             this.saajIterator = saajIterator;
386         }
387 
388         public boolean hasNext() {
389             return saajIterator.hasNext();
390         }
391 
392         public Attachment next() {
393             AttachmentPart saajAttachment = saajIterator.next();
394             return new SaajAttachment(saajAttachment);
395         }
396 
397         public void remove() {
398             saajIterator.remove();
399         }
400     }
401 
402 }