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