View Javadoc
1   package org.andromda.cartridges.support.webservice.client;
2   
3   import java.lang.reflect.Method;
4   import java.net.URL;
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.HashMap;
8   import java.util.Iterator;
9   import java.util.LinkedHashSet;
10  import java.util.List;
11  import java.util.Map;
12  import java.util.Set;
13  import javax.wsdl.Definition;
14  import javax.wsdl.Port;
15  import javax.wsdl.Service;
16  import javax.wsdl.extensions.soap.SOAPAddress;
17  import javax.wsdl.factory.WSDLFactory;
18  import javax.wsdl.xml.WSDLReader;
19  import javax.xml.namespace.QName;
20  import org.apache.axiom.om.OMElement;
21  import org.apache.axis2.AxisFault;
22  import org.apache.axis2.addressing.EndpointReference;
23  import org.apache.axis2.client.ServiceClient;
24  import org.apache.axis2.client.async.Callback;
25  import org.apache.axis2.transport.http.HTTPConstants;
26  import org.apache.axis2.transport.http.HttpTransportProperties.Authenticator;
27  import org.apache.commons.lang.StringUtils;
28  import org.xml.sax.InputSource;
29  
30  /**
31   * A webservice client using Axis2 libraries that allows you to invoke operations on wrapped
32   * style services.
33   *
34   * @author Chad Brandon
35   */
36  public class WebServiceClient
37  {
38      /**
39       * The actual class to use for the webservice.
40       */
41      private Class serviceClass;
42  
43      /**
44       * The WSDL definition.
45       */
46      private Definition definition;
47  
48      /**
49       * The WSDL url.
50       */
51      private String wsdlUrl;
52  
53      /**
54       * The monitor used for synchronization of the definition (to make it thread-safe).
55       */
56      private final Object definitionMonitor = new Object();
57  
58      /**
59       * The underlying service client.
60       */
61      private ServiceClient serviceClient;
62  
63      /**
64       * The optional TypeMapper instance to set.
65       */
66      private TypeMapper typeMapper = new DefaultTypeMapper();
67  
68      /**
69       * Constructs a new client taking a WSDL and serviceClass
70       *
71       * @param wsdlUrl the URL to the WSDL for the service.
72       * @param serviceClass the class used for communicating with the service (i.e. can be a regular java object).
73       */
74      public WebServiceClient(
75          final String wsdlUrl,
76          final Class serviceClass)
77      {
78          this(wsdlUrl, serviceClass, null, null);
79      }
80  
81      /**
82       * Constructs a new client taking a WSDL, the serviceClass and username and password.
83       *
84       * @param wsdlUrl the URL to the WSDL for the service.
85       * @param serviceClass the class used for communicating with the service (i.e. can be a regular java object).
86       * @param username the username to access to the service (if its protected by basic auth).
87       * @param password the password to access the service (if its protected by basic auth).
88       */
89      public WebServiceClient(
90          final String wsdlUrl,
91          final Class serviceClass,
92          final String username,
93          final String password)
94      {
95          this(wsdlUrl, null, serviceClass, username, password);
96      }
97  
98      /**
99       * Constructs a new client taking a WSDL, endpointAddress, serviceClass and username and password.
100      *
101      * @param wsdlUrl the URL to the WSDL for the service.
102      * @param endpointAddress the "end point" or "port" address of the service (if null, then the one in the WSDL will be used).
103      * @param serviceClass the class used for communicating with the service (i.e. can be a regular java object).
104      * @param username the username to access to the service (if its protected by basic auth).
105      * @param password the password to access the service (if its protected by basic auth).
106      */
107     public WebServiceClient(
108         final String wsdlUrl,
109         final String endpointAddress,
110         final Class serviceClass,
111         final String username,
112         final String password)
113     {
114         try
115         {
116             this.serviceClient = new ServiceClient();
117             this.serviceClass = serviceClass;
118             this.wsdlUrl = wsdlUrl;
119             final WSDLReader reader = WSDLFactory.newInstance().newWSDLReader();
120             if (StringUtils.isNotBlank(username))
121             {
122                 final Authenticator authenticator = new Authenticator();
123                 final List<String> authorizationSchemes = new ArrayList<String>();
124                 authorizationSchemes.add(Authenticator.BASIC);
125                 authenticator.setAuthSchemes(authorizationSchemes);
126                 authenticator.setUsername(username);
127                 authenticator.setPassword(password);
128                 authenticator.setPreemptiveAuthentication(true);
129                 this.serviceClient.getOptions().setProperty(
130                     HTTPConstants.AUTHENTICATE,
131                     authenticator);
132                 synchronized (this.definitionMonitor)
133                 {
134                     this.definition =
135                         this.readProtectedWsdl(
136                             reader,
137                             wsdlUrl,
138                             username,
139                             password);
140                 }
141             }
142             else
143             {
144                 synchronized (this.definitionMonitor)
145                 {
146                     this.definition = reader.readWSDL(wsdlUrl);
147                 }
148             }
149             String portAddress;
150             if (StringUtils.isNotBlank(endpointAddress))
151             {
152                 portAddress = endpointAddress;
153             }
154             else
155             {
156                 portAddress = this.findEndPointAddress();
157             }
158             serviceClient.setTargetEPR(new EndpointReference(portAddress));
159         }
160         catch (Exception exception)
161         {
162             this.handleException(exception);
163         }
164     }
165 
166     /**
167      * Sets the optional object creator implementation.
168      *
169      * @param typeMapper the type mapper used for mapping types.
170      */
171     public void setTypeMapper(final TypeMapper typeMapper)
172     {
173         if (typeMapper == null)
174         {
175             throw new IllegalArgumentException("'typeMapper' can not be null");
176         }
177         this.typeMapper = typeMapper;
178     }
179 
180     /**
181      * Sets the timeout in seconds.
182      *
183      * @param seconds
184      */
185     public void setTimeout(long seconds)
186     {
187         this.serviceClient.getOptions().setTimeOutInMilliSeconds(seconds * 1000);
188     }
189 
190     /**
191      * Reads a WSDL protected by basic authentication.
192      *
193      * @param reader the WSDL reader to use for reading the WSDL.
194      * @param address the address of the WSDL.
195      * @param username the username to authenticate with.
196      * @param password the password to authenticate with.
197      * @return the WSDL definition
198      */
199     private Definition readProtectedWsdl(
200         final WSDLReader reader,
201         String address,
202         String username,
203         String password)
204     {
205         Definition definition = null;
206         try
207         {
208             final org.apache.commons.httpclient.HttpClient client = new org.apache.commons.httpclient.HttpClient();
209             final org.apache.commons.httpclient.params.HttpClientParams params =
210                 new org.apache.commons.httpclient.params.HttpClientParams();
211             params.setAuthenticationPreemptive(true);
212             client.setParams(params);
213 
214             final org.apache.commons.httpclient.Credentials credentials =
215                 new org.apache.commons.httpclient.UsernamePasswordCredentials(username, password);
216             final org.apache.commons.httpclient.auth.AuthScope scope =
217                 new org.apache.commons.httpclient.auth.AuthScope(
218                     new URL(address).getHost(),
219                     org.apache.commons.httpclient.auth.AuthScope.ANY_PORT,
220                     org.apache.commons.httpclient.auth.AuthScope.ANY_REALM);
221             client.getState().setCredentials(
222                 scope,
223                 credentials);
224 
225             final org.apache.commons.httpclient.methods.GetMethod get =
226                 new org.apache.commons.httpclient.methods.GetMethod(address);
227             get.setDoAuthentication(true);
228 
229             int status = client.executeMethod(get);
230             if (status == 404)
231             {
232                 throw new WebServiceClientException("WSDL could not be found at: '" + address + '\'');
233             }
234             InputSource inputSource = null;
235             boolean authenticated = status > 0 && status < 400;
236             if (authenticated)
237             {
238                 inputSource = new InputSource(get.getResponseBodyAsStream());
239             }
240             else
241             {
242                 throw new WebServiceClientException("Could not authenticate user: '" + username + "' to WSDL: '" +
243                     address + '\'');
244             }
245             definition =
246                 reader.readWSDL(
247                     address,
248                     inputSource);
249             get.releaseConnection();
250         }
251         catch (Exception exception)
252         {
253             this.handleException(exception);
254         }
255         return definition;
256     }
257 
258     /**
259      * Finds the end point address of the service.
260      *
261      * @return the service end point address.
262      */
263     private String findEndPointAddress()
264     {
265         String address = null;
266         Map services;
267         synchronized (this.definitionMonitor)
268         {
269             services = this.definition.getServices();
270         }
271         if (services != null)
272         {
273             for (final Iterator iterator = services.keySet().iterator(); iterator.hasNext();)
274             {
275                 final QName name = (QName)iterator.next();
276                 if (this.serviceClass.getSimpleName().equals(name.getLocalPart()))
277                 {
278                     final Service service = (Service)services.get(name);
279                     final Map ports = service.getPorts();
280                     for (final Iterator portIterator = ports.keySet().iterator(); portIterator.hasNext();)
281                     {
282                         final String portName = (String)portIterator.next();
283                         final Port port = (Port)ports.get(portName);
284                         for (final Iterator addressIterator = port.getExtensibilityElements().iterator();
285                             addressIterator.hasNext();)
286                         {
287                             final Object element = addressIterator.next();
288                             if (element instanceof SOAPAddress)
289                             {
290                                 address = ((SOAPAddress)element).getLocationURI();
291                             }
292                         }
293                     }
294                 }
295             }
296         }
297         return address;
298     }
299 
300     /**
301      * Invokes the operation identified by the given <code>operationName</code> with the
302      * given <code>arguments</code>.
303      *
304      * @param operationName the name of the operation to invoke.
305      * @param arguments the arguments of the operation.
306      * @return invoke result
307      * @see org.andromda.cartridges.support.webservice.client.Axis2ClientUtils#deserialize(OMElement, Class, TypeMapper)
308      */
309     public Object invokeBlocking(
310         String operationName,
311         Object[] arguments)
312     {
313         final Method method = this.getMethod(
314                 operationName,
315                 arguments);
316         OMElement omElement = this.getOperationElement(method, arguments);
317         Object result = null;
318         try
319         {
320             OMElement response = this.serviceClient.sendReceive(omElement);
321             if (method.getReturnType() != void.class)
322             {
323                 result =
324                     Axis2ClientUtils.deserialize(
325                         response.getFirstElement(),
326                         method.getReturnType(),
327                         this.typeMapper);
328             }
329             omElement = null;
330             response = null;
331         }
332         catch (Exception exception)
333         {
334             this.handleException(exception);
335         }
336         return result;
337     }
338 
339     /**
340      * Retrieves the operation element from the internal WSDL definition.  If it can't be found
341      * an exception is thrown indicating the name of the operation not found.
342      *
343      * @param method the method to invoke.
344      * @param arguments the arguments of the method
345      * @return the found operation element
346      */
347     private OMElement getOperationElement(final Method method, Object[] arguments)
348     {
349         OMElement element = null;
350         synchronized (this.definitionMonitor)
351         {
352             element =
353                 Axis2ClientUtils.getOperationOMElement(
354                     this.definition,
355                     method,
356                     arguments,
357                     this.typeMapper);
358         }
359         if (element == null)
360         {
361             throw new WebServiceClientException("No operation named '" + method.getName()
362                 + "' was found in WSDL: " + this.wsdlUrl);
363         }
364         return element;
365     }
366 
367     /**
368      * Invoke the nonblocking/Asynchronous call
369      *
370      * @param operationName
371      * @param arguments
372      * @param callback
373      */
374     public void invokeNonBlocking(
375         String operationName,
376         Object[] arguments,
377         Callback callback)
378     {
379         final Method method = this.getMethod(
380                 operationName,
381                 arguments);
382         OMElement omElement = this.getOperationElement(method, arguments);
383         try
384         {
385             this.serviceClient.sendReceiveNonBlocking(
386                 omElement,
387                 callback);
388         }
389         catch (AxisFault exception)
390         {
391             this.handleException(exception);
392         }
393         omElement = null;
394     }
395 
396     /**
397      * @param operationName
398      * @param arguments
399      */
400     public void invokeRobust(
401         String operationName,
402         Object[] arguments)
403     {
404         final Method method = this.getMethod(
405                 operationName,
406                 arguments);
407         OMElement omElement = this.getOperationElement(method, arguments);
408         try
409         {
410             this.serviceClient.sendRobust(omElement);
411         }
412         catch (AxisFault exception)
413         {
414             this.handleException(exception);
415         }
416         omElement = null;
417     }
418 
419     /**
420      * Reclaims any resources used by the client.
421      */
422     public void cleanup()
423     {
424         try
425         {
426             this.serviceClient.cleanup();
427         }
428         catch (AxisFault exception)
429         {
430             this.handleException(exception);
431         }
432     }
433 
434     /**
435      * Stores the methods found on the {@link #serviceClass}.
436      */
437     private Map<String, Method> methods = new HashMap<String, Method>();
438 
439     private Method getMethod(
440         final String name,
441         final Object[] arguments)
442     {
443         Method found = methods.get(name);
444         if (found == null)
445         {
446             for (final Iterator iterator = this.getAllMethods().iterator(); iterator.hasNext();)
447             {
448                 final Method method = (Method)iterator.next();
449                 if (method.getName().equals(name) && arguments.length == method.getParameterTypes().length)
450                 {
451                     found = method;
452                     this.methods.put(
453                         name,
454                         found);
455                     break;
456                 }
457             }
458         }
459         return found;
460     }
461 
462     /**
463      * Loads all methods from the given <code>clazz</code> (this includes
464      * all super class methods, public, private and protected).
465      *
466      * @param clazz the class to retrieve the methods.
467      * @return the loaded methods.
468      */
469     private List getAllMethods()
470     {
471         final Set<Method> methods = new LinkedHashSet<Method>();
472         loadMethods(
473             this.serviceClass,
474             methods);
475         return new ArrayList<Method>(methods);
476     }
477 
478     /**
479      * Loads all methods from the given <code>clazz</code> (this includes
480      * all super class methods).
481      *
482      * @param methods the list to load full of methods.
483      * @param clazz the class to retrieve the methods.
484      */
485     private void loadMethods(
486         final Class clazz,
487         final Set<Method> methods)
488     {
489         methods.addAll(Arrays.asList(clazz.getDeclaredMethods()));
490         if (clazz.getSuperclass() != null)
491         {
492             loadMethods(
493                 clazz.getSuperclass(),
494                 methods);
495         }
496     }
497 
498     /**
499      * Appropriate wraps or just re-throws the exception if already an instance
500      * of {@link WebServiceClientException}.
501      *
502      * @param exception the exception to wrap or re-throw as a WebServiceClientException
503      */
504     private void handleException(Exception exception)
505     {
506         if (!(exception instanceof WebServiceClientException))
507         {
508             exception = new WebServiceClientException(exception);
509         }
510         throw (WebServiceClientException)exception;
511     }
512 }