View Javadoc

1   /*
2    * Copyright 2006 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.support;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.lang.reflect.Method;
22  import java.lang.reflect.Modifier;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Map;
27  import javax.xml.namespace.QName;
28  import javax.xml.soap.MessageFactory;
29  import javax.xml.soap.MimeHeaders;
30  import javax.xml.soap.Name;
31  import javax.xml.soap.SOAPConstants;
32  import javax.xml.soap.SOAPElement;
33  import javax.xml.soap.SOAPEnvelope;
34  import javax.xml.soap.SOAPException;
35  import javax.xml.soap.SOAPMessage;
36  
37  import org.w3c.dom.Element;
38  import org.apache.commons.logging.Log;
39  import org.apache.commons.logging.LogFactory;
40  
41  import org.springframework.core.io.Resource;
42  import org.springframework.util.Assert;
43  import org.springframework.util.ClassUtils;
44  import org.springframework.util.StringUtils;
45  import org.springframework.ws.soap.saaj.SaajSoapMessageException;
46  import org.springframework.ws.transport.TransportConstants;
47  import org.springframework.xml.namespace.QNameUtils;
48  
49  /**
50   * Collection of generic utility methods to work with SAAJ. Includes conversion from SAAJ {@link Name} objects to {@link
51   * QName}s and vice-versa, and SAAJ version checking.
52   *
53   * @author Arjen Poutsma
54   * @see Name
55   * @see QName
56   * @since 1.0.0
57   */
58  public abstract class SaajUtils {
59  
60      // The exception message thrown by WebLogic 9
61      private static final String WEBLOGIC_9_SAAJ_EXCEPTION_MESSAGE = "This class does not support SAAJ 1.1";
62  
63      private static final Log logger = LogFactory.getLog(SaajUtils.class);
64  
65      public static final int SAAJ_11 = 0;
66  
67      public static final int SAAJ_12 = 1;
68  
69      public static final int SAAJ_13 = 2;
70  
71      private static final String SAAJ_13_CLASS_NAME = "javax.xml.soap.SAAJMetaFactory";
72  
73      // Maps SOAPElement class names to Integer SAAJ versions (SAAJ_11, SAAJ_12, SAAJ_13)
74      private static final Map saajVersions = Collections.synchronizedMap(new HashMap());
75  
76      private static int saajVersion = SAAJ_12;
77  
78      static {
79          if (isSaaj13()) {
80              saajVersion = SAAJ_13;
81          }
82          else if (isSaaj12()) {
83              saajVersion = SAAJ_12;
84          }
85          else {
86              saajVersion = SAAJ_11;
87          }
88      }
89  
90      private static boolean isSaaj13() {
91          try {
92              ClassUtils.forName(SAAJ_13_CLASS_NAME);
93              MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
94              return true;
95          }
96          catch (ClassNotFoundException ex) {
97              return false;
98          }
99          catch (NoSuchMethodError ex) {
100             return false;
101         }
102         catch (SOAPException ex) {
103             return false;
104         }
105     }
106 
107     /**
108      * Checks whether we can find a SAAJ 1.2 implementation, being aware of the broken WebLogic 9 SAAJ implementation.
109      * <p/>
110      * WebLogic 9 does implement SAAJ 1.2, but throws UnsupportedOperationExceptions when a SAAJ 1.2 method is called.
111      */
112     private static boolean isSaaj12() {
113         if (Element.class.isAssignableFrom(SOAPElement.class)) {
114             // see if we are dealing with WebLogic 9
115             try {
116                 MessageFactory messageFactory = MessageFactory.newInstance();
117                 SOAPMessage message = messageFactory.createMessage();
118                 try {
119                     message.getSOAPBody();
120                     return true;
121                 }
122                 catch (UnsupportedOperationException ex) {
123                     return false;
124                 }
125             }
126             catch (SOAPException e) {
127                 return false;
128             }
129         }
130         else {
131             return false;
132         }
133     }
134 
135     /**
136      * Gets the SAAJ version.
137      *
138      * @return a code comparable to the SAAJ_XX codes in this class
139      * @see #SAAJ_11
140      * @see #SAAJ_12
141      * @see #SAAJ_13
142      */
143     public static int getSaajVersion() {
144         return saajVersion;
145     }
146 
147     /**
148      * Gets the SAAJ version for the specified {@link SOAPMessage}.
149      *
150      * @return a code comparable to the SAAJ_XX codes in this class
151      * @see #SAAJ_11
152      * @see #SAAJ_12
153      * @see #SAAJ_13
154      */
155     public static int getSaajVersion(SOAPMessage soapMessage) {
156         Assert.notNull(soapMessage, "'soapMessage' must not be null");
157         SOAPEnvelope soapEnvelope;
158         try {
159         	soapEnvelope = soapMessage.getSOAPPart().getEnvelope();
160         }
161         catch (SOAPException ex) {
162         	throw new SaajSoapMessageException("Could not access envelope: " + ex.getMessage(), ex);
163         }
164         return getSaajVersion(soapEnvelope);
165     }
166 
167     /**
168      * Gets the SAAJ version for the specified {@link javax.xml.soap.SOAPElement}.
169      *
170      * @return a code comparable to the SAAJ_XX codes in this class
171      * @see #SAAJ_11
172      * @see #SAAJ_12
173      * @see #SAAJ_13
174      */
175     public static int getSaajVersion(SOAPElement soapElement) {
176         Assert.notNull(soapElement, "'soapElement' must not be null");
177         Class soapElementClass = soapElement.getClass();
178         Integer saajVersion = (Integer) saajVersions.get(soapElementClass.getName());
179         if (saajVersion == null) {
180             if (isSaaj12(soapElement)) {
181                 if (isSaaj13(soapElement)) {
182                     saajVersion = new Integer(SAAJ_13);
183                 }
184                 else {
185                     saajVersion = new Integer(SAAJ_12);
186                 }
187             } else {
188                 saajVersion = new Integer(SAAJ_11);
189             }
190             saajVersions.put(soapElementClass.getName(), saajVersion);
191             if (logger.isTraceEnabled()) {
192                 logger.trace("SOAPElement [" + soapElement.getClass().getName() + "] implements " +
193                         getSaajVersionString(saajVersion.intValue()));
194             }
195         }
196         return saajVersion.intValue();
197     }
198 
199     private static boolean isSaaj13(SOAPElement soapElement) {
200         try {
201             Method m = soapElement.getClass().getMethod("getElementQName", new Class[0]);
202             // we might be using the SAAJ 1.3 API, while the impl is 1.2
203             // let's see if the method is not abstract
204             if (Modifier.isAbstract(m.getModifiers())) {
205                 logger.warn("Detected SAAJ API version 1.3, while implementation provides version 1.2. " +
206                         "Please replace the SAAJ API jar with a version that corresponds to your runtime" +
207                         " implementation (which might be provided by your app server).");
208                 return false;
209             }
210             else {
211                 return true;
212             }
213         }
214         catch (NoSuchMethodException e) {
215             // getElementQName not found
216             return false;
217         }
218     }
219 
220     /**
221      * Checks whether we can find a SAAJ 1.2 implementation, being aware of the broken WebLogic 9 SAAJ implementation.
222      * <p/>
223      * WebLogic 9 does implement SAAJ 1.2, but throws UnsupportedOperationExceptions when a SAAJ 1.2 method is called.
224      */
225     private static boolean isSaaj12(SOAPElement soapElement) {
226         try {
227             Method m = soapElement.getClass().getMethod("getPrefix", new Class[0]);
228             // we might be using the SAAJ 1.2 API, while the impl is 1.1
229             // let's see if the method is not abstract
230             if (Modifier.isAbstract(m.getModifiers())) {
231                 logger.warn("Detected SAAJ API version 1.2, while implementation provides version 1.1. " +
232                         "Please replace the SAAJ API jar with a version that corresponds to your runtime " +
233                         "implementation (which might be provided by your app server).");
234                 return false;
235             }
236             else {
237                 soapElement.getPrefix();
238                 return true;
239             }
240         }
241         catch (NoSuchMethodException e) {
242             // getPrefix not found
243             return false;
244         }
245         catch (UnsupportedOperationException ex) {
246             // getPrefix results in UOE, let's see if we're dealing with WL 9
247             if (WEBLOGIC_9_SAAJ_EXCEPTION_MESSAGE.equals(ex.getMessage())) {
248                 return false;
249             } else {
250                 throw ex;
251             }
252         }
253     }
254 
255     /**
256      * Returns the SAAJ version as a String. The returned string will be "<code>SAAJ 1.3</code>", "<code>SAAJ
257      * 1.2</code>", or "<code>SAAJ 1.1</code>".
258      *
259      * @return a string representation of the SAAJ version
260      * @see #getSaajVersion()
261      */
262     public static String getSaajVersionString() {
263         return getSaajVersionString(saajVersion);
264     }
265 
266     private static String getSaajVersionString(int saajVersion) {
267         if (saajVersion >= SaajUtils.SAAJ_13) {
268             return "SAAJ 1.3";
269         }
270         else if (saajVersion == SaajUtils.SAAJ_12) {
271             return "SAAJ 1.2";
272         }
273         else if (saajVersion == SaajUtils.SAAJ_11) {
274             return "SAAJ 1.1";
275         }
276         else {
277             return "";
278         }
279     }
280 
281     /**
282      * Converts a {@link QName} to a {@link Name}. A {@link SOAPElement} is required to resolve namespaces.
283      *
284      * @param qName          the <code>QName</code> to convert
285      * @param resolveElement a <code>SOAPElement</code> used to resolve namespaces to prefixes
286      * @return the converted SAAJ Name
287      * @throws SOAPException            if conversion is unsuccessful
288      * @throws IllegalArgumentException if <code>qName</code> is not fully qualified
289      */
290     public static Name toName(QName qName, SOAPElement resolveElement) throws SOAPException {
291         String qNamePrefix = QNameUtils.getPrefix(qName);
292         SOAPEnvelope envelope = getEnvelope(resolveElement);
293         if (StringUtils.hasLength(qName.getNamespaceURI()) && StringUtils.hasLength(qNamePrefix)) {
294             return envelope.createName(qName.getLocalPart(), qNamePrefix, qName.getNamespaceURI());
295         }
296         else if (StringUtils.hasLength(qName.getNamespaceURI())) {
297             Iterator prefixes;
298             if (getSaajVersion(resolveElement) == SAAJ_11) {
299                 prefixes = resolveElement.getNamespacePrefixes();
300             }
301             else {
302                 prefixes = resolveElement.getVisibleNamespacePrefixes();
303             }
304             while (prefixes.hasNext()) {
305                 String prefix = (String) prefixes.next();
306                 if (qName.getNamespaceURI().equals(resolveElement.getNamespaceURI(prefix))) {
307                     return envelope.createName(qName.getLocalPart(), prefix, qName.getNamespaceURI());
308                 }
309             }
310             return envelope.createName(qName.getLocalPart(), "", qName.getNamespaceURI());
311         }
312         else {
313             return envelope.createName(qName.getLocalPart());
314         }
315     }
316 
317     /**
318      * Converts a <code>javax.xml.soap.Name</code> to a <code>javax.xml.namespace.QName</code>.
319      *
320      * @param name the <code>Name</code> to convert
321      * @return the converted <code>QName</code>
322      */
323     public static QName toQName(Name name) {
324         if (StringUtils.hasLength(name.getURI()) && StringUtils.hasLength(name.getPrefix())) {
325             return QNameUtils.createQName(name.getURI(), name.getLocalName(), name.getPrefix());
326         }
327         else if (StringUtils.hasLength(name.getURI())) {
328             return new QName(name.getURI(), name.getLocalName());
329         }
330         else {
331             return new QName(name.getLocalName());
332         }
333     }
334 
335     /**
336      * Loads a SAAJ <code>SOAPMessage</code> from the given resource with a given message factory.
337      *
338      * @param resource       the resource to read from
339      * @param messageFactory SAAJ message factory used to construct the message
340      * @return the loaded SAAJ message
341      * @throws SOAPException if the message cannot be constructed
342      * @throws IOException   if the input stream resource cannot be loaded
343      */
344     public static SOAPMessage loadMessage(Resource resource, MessageFactory messageFactory)
345             throws SOAPException, IOException {
346         InputStream is = resource.getInputStream();
347         try {
348             MimeHeaders mimeHeaders = new MimeHeaders();
349             mimeHeaders.addHeader(TransportConstants.HEADER_CONTENT_TYPE, "text/xml");
350             mimeHeaders.addHeader(TransportConstants.HEADER_CONTENT_LENGTH, Long.toString(resource.getFile().length()));
351             return messageFactory.createMessage(mimeHeaders, is);
352         }
353         finally {
354             is.close();
355         }
356     }
357 
358     /**
359      * Returns the SAAJ <code>SOAPEnvelope</code> for the given element.
360      *
361      * @param element the element to return the envelope from
362      * @return the envelope, or <code>null</code> if not found
363      */
364     public static SOAPEnvelope getEnvelope(SOAPElement element) {
365         Assert.notNull(element, "Element should not be null");
366         do {
367             if (element instanceof SOAPEnvelope) {
368                 return (SOAPEnvelope) element;
369             }
370             element = element.getParentElement();
371         }
372         while (element != null);
373         return null;
374     }
375 }