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.saaj;
18  
19  import java.io.BufferedInputStream;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.PushbackInputStream;
23  import java.util.Iterator;
24  import java.util.StringTokenizer;
25  import javax.xml.soap.MessageFactory;
26  import javax.xml.soap.MimeHeaders;
27  import javax.xml.soap.SOAPConstants;
28  import javax.xml.soap.SOAPException;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  
33  import org.springframework.beans.factory.InitializingBean;
34  import org.springframework.util.StringUtils;
35  import org.springframework.ws.WebServiceMessage;
36  import org.springframework.ws.soap.SoapMessageCreationException;
37  import org.springframework.ws.soap.SoapMessageFactory;
38  import org.springframework.ws.soap.SoapVersion;
39  import org.springframework.ws.soap.saaj.support.SaajUtils;
40  import org.springframework.ws.transport.TransportConstants;
41  import org.springframework.ws.transport.TransportInputStream;
42  
43  /**
44   * SAAJ-specific implementation of the {@link org.springframework.ws.WebServiceMessageFactory WebServiceMessageFactory}.
45   * Wraps a SAAJ {@link MessageFactory}. This factory will use SAAJ 1.3 when found, or fall back to SAAJ 1.2 or even
46   * 1.1.
47   * <p/>
48   * A SAAJ {@link MessageFactory} can be injected to the {@link #SaajSoapMessageFactory(javax.xml.soap.MessageFactory)
49   * constructor}, or by the {@link #setMessageFactory(javax.xml.soap.MessageFactory)} property. When a SAAJ message
50   * factory is injected, the {@link #setSoapVersion(org.springframework.ws.soap.SoapVersion)} property is ignored.
51   *
52   * @author Arjen Poutsma
53   * @see org.springframework.ws.soap.saaj.SaajSoapMessage
54   * @since 1.0.0
55   */
56  public class SaajSoapMessageFactory implements SoapMessageFactory, InitializingBean {
57  
58      private static final Log logger = LogFactory.getLog(SaajSoapMessageFactory.class);
59  
60      private MessageFactory messageFactory;
61  
62      private String messageFactoryProtocol;
63  
64      private boolean langAttributeOnSoap11FaultString = true;
65  
66      /** Default, empty constructor. */
67      public SaajSoapMessageFactory() {
68      }
69  
70      /** Constructor that takes a message factory as an argument. */
71      public SaajSoapMessageFactory(MessageFactory messageFactory) {
72          this.messageFactory = messageFactory;
73      }
74  
75      /** Returns the SAAJ <code>MessageFactory</code> used. */
76      public MessageFactory getMessageFactory() {
77          return messageFactory;
78      }
79  
80      /** Sets the SAAJ <code>MessageFactory</code>. */
81      public void setMessageFactory(MessageFactory messageFactory) {
82          this.messageFactory = messageFactory;
83      }
84  
85      /**
86       * Defines whether a {@code xml:lang} attribute should be set on SOAP 1.1 {@code <faultstring>} elements.
87       * <p/>
88       * The default is {@code true}, to comply with WS-I, but this flag can be set to {@code false} to the older W3C SOAP
89       * 1.1 specification.
90       *
91       * @see <a href="http://www.ws-i.org/Profiles/BasicProfile-1.1.html#SOAP_Fault_Language">WS-I Basic Profile 1.1</a>
92       */
93      public void setlangAttributeOnSoap11FaultString(boolean langAttributeOnSoap11FaultString) {
94          this.langAttributeOnSoap11FaultString = langAttributeOnSoap11FaultString;
95      }
96  
97      public void setSoapVersion(SoapVersion version) {
98          if (SaajUtils.getSaajVersion() >= SaajUtils.SAAJ_13) {
99              if (SoapVersion.SOAP_11 == version) {
100                 messageFactoryProtocol = SOAPConstants.SOAP_1_1_PROTOCOL;
101             }
102             else if (SoapVersion.SOAP_12 == version) {
103                 messageFactoryProtocol = SOAPConstants.SOAP_1_2_PROTOCOL;
104             }
105             else {
106                 throw new IllegalArgumentException(
107                         "Invalid version [" + version + "]. Expected the SOAP_11 or SOAP_12 constant");
108             }
109         }
110         else if (SoapVersion.SOAP_11 != version) {
111             throw new IllegalArgumentException("SAAJ 1.1 and 1.2 only support SOAP 1.1");
112         }
113     }
114 
115     public void afterPropertiesSet() throws Exception {
116         if (messageFactory == null) {
117             try {
118                 if (SaajUtils.getSaajVersion() >= SaajUtils.SAAJ_13) {
119                     if (!StringUtils.hasLength(messageFactoryProtocol)) {
120                         messageFactoryProtocol = SOAPConstants.SOAP_1_1_PROTOCOL;
121                     }
122                     if (logger.isInfoEnabled()) {
123                         logger.info("Creating SAAJ 1.3 MessageFactory with " + messageFactoryProtocol);
124                     }
125                     messageFactory = MessageFactory.newInstance(messageFactoryProtocol);
126                 }
127                 else if (SaajUtils.getSaajVersion() == SaajUtils.SAAJ_12) {
128                     logger.info("Creating SAAJ 1.2 MessageFactory");
129                     messageFactory = MessageFactory.newInstance();
130                 }
131                 else if (SaajUtils.getSaajVersion() == SaajUtils.SAAJ_11) {
132                     logger.info("Creating SAAJ 1.1 MessageFactory");
133                     messageFactory = MessageFactory.newInstance();
134                 }
135                 else {
136                     throw new IllegalStateException(
137                             "SaajSoapMessageFactory requires SAAJ 1.1, which was not found on the classpath");
138                 }
139             }
140             catch (NoSuchMethodError ex) {
141                 throw new SoapMessageCreationException(
142                         "Could not create SAAJ MessageFactory. Is the version of the SAAJ specification interfaces [" +
143                                 SaajUtils.getSaajVersionString() +
144                                 "] the same as the version supported by the application server?", ex);
145             }
146             catch (SOAPException ex) {
147                 throw new SoapMessageCreationException("Could not create SAAJ MessageFactory: " + ex.getMessage(), ex);
148             }
149         }
150         if (logger.isDebugEnabled()) {
151             logger.debug("Using MessageFactory class [" + messageFactory.getClass().getName() + "]");
152         }
153     }
154 
155     public WebServiceMessage createWebServiceMessage() {
156         try {
157             return new SaajSoapMessage(messageFactory.createMessage(), langAttributeOnSoap11FaultString);
158         }
159         catch (SOAPException ex) {
160             throw new SoapMessageCreationException("Could not create empty message: " + ex.getMessage(), ex);
161         }
162     }
163 
164     public WebServiceMessage createWebServiceMessage(InputStream inputStream) throws IOException {
165         MimeHeaders mimeHeaders = parseMimeHeaders(inputStream);
166         try {
167             inputStream = checkForUtf8ByteOrderMark(inputStream);
168             return new SaajSoapMessage(messageFactory.createMessage(mimeHeaders, inputStream));
169         }
170         catch (SOAPException ex) {
171             // SAAJ 1.3 RI has a issue with handling multipart XOP content types which contain "startinfo" rather than
172             // "start-info", so let's try and do something about it
173             String contentType = StringUtils
174                     .arrayToCommaDelimitedString(mimeHeaders.getHeader(TransportConstants.HEADER_CONTENT_TYPE));
175             if (contentType.indexOf("startinfo") != -1) {
176                 contentType = contentType.replace("startinfo", "start-info");
177                 mimeHeaders.setHeader(TransportConstants.HEADER_CONTENT_TYPE, contentType);
178                 try {
179                     return new SaajSoapMessage(messageFactory.createMessage(mimeHeaders, inputStream),
180                             langAttributeOnSoap11FaultString);
181                 }
182                 catch (SOAPException e) {
183                     // fall-through
184                 }
185             }
186             throw new SoapMessageCreationException("Could not create message from InputStream: " + ex.getMessage(), ex);
187         }
188     }
189 
190     private MimeHeaders parseMimeHeaders(InputStream inputStream) throws IOException {
191         MimeHeaders mimeHeaders = new MimeHeaders();
192         if (inputStream instanceof TransportInputStream) {
193             TransportInputStream transportInputStream = (TransportInputStream) inputStream;
194             for (Iterator headerNames = transportInputStream.getHeaderNames(); headerNames.hasNext();) {
195                 String headerName = (String) headerNames.next();
196                 for (Iterator headerValues = transportInputStream.getHeaders(headerName); headerValues.hasNext();) {
197                     String headerValue = (String) headerValues.next();
198                     StringTokenizer tokenizer = new StringTokenizer(headerValue, ",");
199                     while (tokenizer.hasMoreTokens()) {
200                         mimeHeaders.addHeader(headerName, tokenizer.nextToken().trim());
201                     }
202                 }
203             }
204         }
205         return mimeHeaders;
206     }
207 
208     /**
209      * Checks for the UTF-8 Byte Order Mark, and removes it if present. The SAAJ RI cannot cope with these BOMs.
210      *
211      * @see <a href="http://jira.springframework.org/browse/SWS-393">SWS-393</a>
212      * @see <a href="http://unicode.org/faq/utf_bom.html#22">UTF-8 BOMs</a>
213      */
214     private InputStream checkForUtf8ByteOrderMark(InputStream inputStream) throws IOException {
215         PushbackInputStream pushbackInputStream = new PushbackInputStream(new BufferedInputStream(inputStream), 3);
216         byte[] bom = new byte[3];
217         if (pushbackInputStream.read(bom) != -1) {
218             // check for the UTF-8 BOM, and remove it if there. See SWS-393
219             if (!(bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF)) {
220                 pushbackInputStream.unread(bom);
221             }
222         }
223         return pushbackInputStream;
224     }
225 
226     public String toString() {
227         StringBuffer buffer = new StringBuffer("SaajSoapMessageFactory[");
228         buffer.append(SaajUtils.getSaajVersionString());
229         if (SaajUtils.getSaajVersion() >= SaajUtils.SAAJ_13) {
230             buffer.append(',');
231             buffer.append(messageFactoryProtocol);
232         }
233         buffer.append(']');
234         return buffer.toString();
235     }
236 }