001 package org.andromda.cartridges.support.webservice.client;
002
003 import java.lang.reflect.Method;
004 import java.net.URL;
005 import java.util.ArrayList;
006 import java.util.Arrays;
007 import java.util.HashMap;
008 import java.util.Iterator;
009 import java.util.LinkedHashSet;
010 import java.util.List;
011 import java.util.Map;
012 import java.util.Set;
013 import javax.wsdl.Definition;
014 import javax.wsdl.Port;
015 import javax.wsdl.Service;
016 import javax.wsdl.extensions.soap.SOAPAddress;
017 import javax.wsdl.factory.WSDLFactory;
018 import javax.wsdl.xml.WSDLReader;
019 import javax.xml.namespace.QName;
020 import org.apache.axiom.om.OMElement;
021 import org.apache.axis2.AxisFault;
022 import org.apache.axis2.addressing.EndpointReference;
023 import org.apache.axis2.client.ServiceClient;
024 import org.apache.axis2.client.async.Callback;
025 import org.apache.axis2.transport.http.HTTPConstants;
026 import org.apache.axis2.transport.http.HttpTransportProperties.Authenticator;
027 import org.apache.commons.lang.StringUtils;
028 import org.xml.sax.InputSource;
029
030 /**
031 * A webservice client using Axis2 libraries that allows you to invoke operations on wrapped
032 * style services.
033 *
034 * @author Chad Brandon
035 */
036 public class WebServiceClient
037 {
038 /**
039 * The actual class to use for the webservice.
040 */
041 private Class serviceClass;
042
043 /**
044 * The WSDL definition.
045 */
046 private Definition definition;
047
048 /**
049 * The WSDL url.
050 */
051 private String wsdlUrl;
052
053 /**
054 * The monitor used for synchronization of the definition (to make it thread-safe).
055 */
056 private final Object definitionMonitor = new Object();
057
058 /**
059 * The underlying service client.
060 */
061 private ServiceClient serviceClient;
062
063 /**
064 * The optional TypeMapper instance to set.
065 */
066 private TypeMapper typeMapper = new DefaultTypeMapper();
067
068 /**
069 * Constructs a new client taking a WSDL and serviceClass
070 *
071 * @param wsdlUrl the URL to the WSDL for the service.
072 * @param serviceClass the class used for communicating with the service (i.e. can be a regular java object).
073 */
074 public WebServiceClient(
075 final String wsdlUrl,
076 final Class serviceClass)
077 {
078 this(wsdlUrl, serviceClass, null, null);
079 }
080
081 /**
082 * Constructs a new client taking a WSDL, the serviceClass and username and password.
083 *
084 * @param wsdlUrl the URL to the WSDL for the service.
085 * @param serviceClass the class used for communicating with the service (i.e. can be a regular java object).
086 * @param username the username to access to the service (if its protected by basic auth).
087 * @param password the password to access the service (if its protected by basic auth).
088 */
089 public WebServiceClient(
090 final String wsdlUrl,
091 final Class serviceClass,
092 final String username,
093 final String password)
094 {
095 this(wsdlUrl, null, serviceClass, username, password);
096 }
097
098 /**
099 * 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 }