package org.codehaus.xfire.spring.remoting;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.wsdl.Definition;
import javax.wsdl.factory.WSDLFactory;
import javax.xml.namespace.QName;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.xfire.XFireRuntimeException;
import org.codehaus.xfire.client.Client;
import org.codehaus.xfire.client.XFireProxy;
import org.codehaus.xfire.client.XFireProxyFactory;
import org.codehaus.xfire.handler.Handler;
import org.codehaus.xfire.service.Endpoint;
import org.codehaus.xfire.service.Service;
import org.codehaus.xfire.service.ServiceFactory;
import org.codehaus.xfire.service.binding.ObjectServiceFactory;
import org.codehaus.xfire.transport.Channel;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
/**
* Factory bean to easily create XFire clients via Spring, if the service's Java
* interface is available. Naming of properties is done to be the same as
* {@link org.springframework.remoting.jaxrpc.JaxRpcPortProxyFactoryBean}.
*
* The only mandatory properties to set before using this factory are:
* {@link #setServiceClass(Class)} and {@link #setWsdlDocumentUrl(String)}.
*
* By default this factory bean creates a service endpoint using an instance of
* {@link org.codehaus.xfire.service.binding.ObjectServiceFactory}. Another one can
* be configured using {@link #setServiceFactory(ServiceFactory)}
*
* serviceName and namespaceUri can be derived from the content of the WSDL document
* (if the document only contains one service), but unfortunately that does not (yet)
* work if username/password needs to be supplied to get at the WSDL.
*
* @author Fried Hoeben
*/
public class XFireClientFactoryBean
implements FactoryBean, InitializingBean
{
private static final Log LOG = LogFactory.getLog(XFireClientFactoryBean.class);
// client proxy, in case lookupServiceOnStartup == true
// proxy to the client proxy, otherwise
private Object _serviceProxy;
private Class _serviceClass;
private ServiceFactory _serviceFactory = new ObjectServiceFactory();
private String _wsdlDocumentUrl;
private String _serviceName;
private String _namespaceUri;
private String _username;
private String _password;
private String _url;
private QName _endpointName;
private Map _properties;
private boolean _lookupServiceOnStartup = true;
private List outHandlers = null;
private List inHandlers = null;
private List faultHandlers = null;
public Object getObject()
throws Exception
{
return _serviceProxy;
}
public Class getObjectType()
{
return (_serviceProxy != null) ? _serviceProxy.getClass() : getServiceClass();
}
public boolean isSingleton()
{
return true;
}
public void afterPropertiesSet()
throws Exception
{
if (getServiceClass() == null)
{
throw new IllegalStateException("serviceInterface is required");
}
if (getWsdlDocumentUrl() == null)
{
throw new IllegalStateException("wsdlDocumentUrl is required");
}
ProxyInterceptor interceptor;
if (getLookupServiceOnStartup())
{
// create XFire client proxy directly
_serviceProxy = createClient();
}
else
{
// create proxy for XFire client proxy, this will create the XFire
// client proxy
// when it is first called
interceptor = new ProxyInterceptor();
_serviceProxy = ProxyFactory.getProxy(getServiceClass(), interceptor);
}
}
/**
* @return the service factory that this factory will use
*/
public ServiceFactory getServiceFactory()
{
return _serviceFactory;
}
/**
* Sets the service factory that will be used to create a client. If this method is never
* called an instance of {@link org.codehaus.xfire.service.binding.ObjectServiceFactory} will
* be used.
*
* @param factory
* service factory this factory should use to create a client
*/
public void setServiceFactory(ServiceFactory factory)
{
if (factory == null)
{
throw new IllegalArgumentException("Can not set the service factory to null");
}
_serviceFactory = factory;
}
/**
* @return Returns the service's interface.
*/
public Class getServiceClass()
{
return _serviceClass;
}
/**
* @param serviceClass
* The interface implemented by the service called via the proxy.
*/
public void setServiceClass(Class serviceClass)
{
_serviceClass = serviceClass;
}
/**
* @return Returns the URL where the WSDL to this service can be found.
*/
public String getWsdlDocumentUrl()
{
return _wsdlDocumentUrl;
}
/**
* @param wsdlUrl
* The URL where the WSDL to this service can be found.
*/
public void setWsdlDocumentUrl(String wsdlUrl)
{
_wsdlDocumentUrl = wsdlUrl.trim();
}
/**
* Gets the name of the service. If null the name will be
* looked up from the WSDL, or generated from the interface name by XFire.
*
* @return Returns the serviceName.
*/
public String getServiceName()
{
return _serviceName;
}
/**
* Sets the name of the service to access. If left null the
* name will be looked up from the WSDL, or generated from the interface
* name by XFire.
*
* @param serviceName
* The service name to set.
*/
public void setServiceName(String serviceName)
{
_serviceName = serviceName;
}
/**
* Gets the default namespace for the service. If null the
* namespace will be looked up from the WSDL, or generated from the
* interface package by XFire.
*
* @return Returns the namespace for the service.
*/
public String getNamespaceUri()
{
return _namespaceUri;
}
/**
* Sets the default namespace for the service. If left null
* the namespace will be looked up from the WSDL, or generated from the
* interface package by XFire.
*
* @param namespace
* The namespace to set.
*/
public void setNamespaceUri(String namespace)
{
_namespaceUri = namespace;
}
/**
* Gets whether to look up the XFire service on startup.
*
* @return whether to look up the service on startup.
*/
public boolean getLookupServiceOnStartup()
{
return _lookupServiceOnStartup;
}
/**
* Sets whether to look up the XFire service on startup. Default is
* true.
*
* Can be set to false to allow for late start of the target
* server. In this case, the XFire service client proxy will be created on
* first access. This does add some overhead (on each call) since
* synchronization is used to ensure only one client proxy is ever created,
* furthermore errors in the WSDL document URL are not detected until the
* first call.
*
* @param lookupServiceOnStartup
* whether to look up the service on startup.
*/
public void setLookupServiceOnStartup(boolean lookupServiceOnStartup)
{
_lookupServiceOnStartup = lookupServiceOnStartup;
}
/**
* Gets the username for HTTP basic authentication.
*
* @return Returns the username to send.
*/
public String getUsername()
{
return _username;
}
/**
* Sets the username for HTTP basic authentication.
*
* @param username
* The username to set.
*/
public void setUsername(String username)
{
_username = username;
}
/**
* Gets the password for HTTP basic authentication.
*
* @return Returns the password to send.
*/
public String getPassword()
{
return _password;
}
/**
* Sets the password for HTTP basic authentication.
*
* @param password
* The password to set.
*/
public void setPassword(String password)
{
_password = password;
}
/**
* The properties that will be set on the Client.
*/
public Map getProperties()
{
return _properties;
}
/**
* Set the properties for the Client.
*/
public void setProperties(Map properties)
{
this._properties = properties;
}
public QName getEndpoint()
{
return _endpointName;
}
/**
* Set the name of the Endpoint/Port in the WSDL to use with the Client.
*
* @param name
*/
public void setEndpoint(QName name)
{
_endpointName = name;
}
public String getUrl()
{
return _url;
}
/**
* Set the URL the Client is to invoke. If this is not supplied, the one from the
* WSDL will be used instead.
* @return
*/
public void setUrl(String _url)
{
this._url = _url;
}
/**
* Creates actual XFire client proxy that this interceptor will delegate to.
*
* @throws Exception
* if the client proxy could not be created.
*/
private Object createClient()
throws Exception
{
Object serviceClient = makeClient();
Class interf = getServiceClass();
if (LOG.isDebugEnabled())
{
LOG.debug("Created: " + toString());
}
String username = getUsername();
XFireProxy proxy = (XFireProxy) Proxy.getInvocationHandler(serviceClient);
Client client = proxy.getClient();
if (username != null)
{
client.setProperty(Channel.USERNAME, username);
String password = getPassword();
client.setProperty(Channel.PASSWORD, password);
if (LOG.isDebugEnabled())
{
LOG.debug("Enabled HTTP basic authentication for: " + interf + " with username: "
+ username);
}
}
//configure in and out handlers
this.configureClientHandlers(client);
return serviceClient;
}
/**
* Configures the client with the specified inHandlers, outHandlers and
* faultHandlers.
*
* @param client
*/
private void configureClientHandlers(Client client) {
if( this.inHandlers != null ){
for(int i=0; i < this.inHandlers.size(); i++){
Handler handler = (Handler) this.inHandlers.get(i);
client.addInHandler(handler);
}
}
if( this.outHandlers != null ){
for(int i=0; i < this.outHandlers.size(); i++){
Handler handler = (Handler) this.outHandlers.get(i);
client.addOutHandler(handler);
}
}
if( this.faultHandlers != null ){
for(int i=0; i < this.faultHandlers.size(); i++){
Handler handler = (Handler) this.faultHandlers.get(i);
client.addFaultHandler(handler);
}
}
}
/**
* Performs actual creation of XFire client proxy.
*
* @return XFire proxy to the service
* @throws java.net.MalformedURLException
* if {@link XFireProxyFactory#create} threw one
*/
private Object makeClient()
throws Exception
{
String serviceName = getServiceName();
String namespace = getNamespaceUri();
if ((serviceName == null || namespace == null) && _wsdlDocumentUrl != null)
{
// try to determine properties for name and namespace based on the
// WSDL
setWSDLProperties();
}
Service serviceModel = getServiceFactory().create(getServiceClass(),
serviceName,
namespace,
_properties);
String serviceUrl = getServiceUrl();
if (_endpointName != null)
{
Endpoint ep = serviceModel.getEndpoint(_endpointName);
if (ep == null)
throw new XFireRuntimeException("Could not find endpoint with name " + _endpointName + " on service.");
return new XFireProxyFactory().create(ep);
}
else
return new XFireProxyFactory().create(serviceModel, serviceUrl);
}
private String getServiceUrl()
{
String serviceUrl = _url;
if (serviceUrl == null)
serviceUrl = getWsdlDocumentUrl().replaceAll("\\?wsdl", "").replaceAll("\\?WSDL", "");
return serviceUrl;
}
/**
* Sets additional properties based on the WSDL Document configured.
* Will lookup (and then set) ServiceName and NamespaceUri.
*/
protected void setWSDLProperties()
{
String wsdlUrl = getWsdlDocumentUrl();
try
{
Definition d = getWSDLDefinition();
if (LOG.isDebugEnabled())
{
// if we are not able to parse the WSDL the exception will also
// log the WSDL URL
LOG.debug("Determining properties based on WSDL at: " + wsdlUrl);
}
Map services = d.getServices();
javax.wsdl.Service service = (javax.wsdl.Service) getOnlyElem(services);
if (service != null)
{
if (getServiceName() == null)
{
setServiceName(service.getQName().getLocalPart());
}
if (LOG.isDebugEnabled())
{
LOG.debug("ServiceName is: " + getServiceName());
}
if (getNamespaceUri() == null)
{
setNamespaceUri(service.getQName().getNamespaceURI());
}
if (LOG.isDebugEnabled())
{
LOG.debug("NamespaceUri is: " + getNamespaceUri());
}
}
else
{
LOG.warn("Unable to determine which service is meant. WSDL does not contain " +
"exactly one service, but: " + services.size());
}
}
catch (Exception e)
{
throw new XFireRuntimeException("Unable to parse WSDL at: " + wsdlUrl, e);
}
}
/**
* Gets the Definition contained in the WSDL document (does not currently support reading
* WSDL that is protected with authentication).
* @return Definition describing the service(s)
* @throws Exception if the definition could not be read
*/
protected Definition getWSDLDefinition()
throws Exception
{
return WSDLFactory.newInstance().newWSDLReader().readWSDL(getWsdlDocumentUrl());
}
/**
* Returns the only value in a Map
*
* @param map
* @return the only value in the map, if it contained exactly 1 key/value
* pair
* null, otherwise
*/
private Object getOnlyElem(Map map)
{
if (map.size() == 1)
{
Set keySet = map.keySet();
Iterator i = keySet.iterator();
return map.get(i.next());
}
else
{
return null;
}
}
public String toString()
{
StringBuffer builder = new StringBuffer();
builder.append("XFire client proxy for: ");
builder.append(getServiceClass());
builder.append(" at: ");
builder.append(getServiceUrl());
return builder.toString();
}
/**
* Interceptor for (i.e. proxy to) the actual XFire client proxy. This class
* performs lazy initialization of the XFire client proxy, which can come in
* handy if the service is not guaranteed to be available by the time the
* factory bean is used to created an instance, but will be available by the
* time the client is actually called.
*
* This does add some overhead since synchronization is used to ensure only * one client is ever allocated. Furthermore if there is a problem accessing * the service it is not detected until the first call. */ private class ProxyInterceptor implements MethodInterceptor { // actual XFire client proxy private Object _serviceClient; public Object invoke(MethodInvocation invocation) throws Throwable { if (_serviceClient == null) { if (AopUtils.isToStringMethod(invocation.getMethod())) { // do not lookup service for toString() return "Un-initialized " + XFireClientFactoryBean.this.toString(); } } Method method = invocation.getMethod(); Object[] args = invocation.getArguments(); Object client = getClient(); try { return method.invoke(client, args); } catch (InvocationTargetException e) { StringBuffer callTarget = new StringBuffer(getServiceUrl()).append(" arguments: "); for(int x = 0 ; x < args.length ; x ++ ) { callTarget.append(args[x].getClass().getName()).append(" : ").append(args[x].toString()).append(" |"); } Throwable targetException = e.getTargetException(); if (targetException instanceof XFireRuntimeException) { // convert XFire runtime exception to one detailing call // made XFireRuntimeException xfRt = (XFireRuntimeException) targetException; Throwable cause = xfRt.getCause(); throw new XFireRuntimeException("Exception while calling: " + callTarget.toString(), cause); } throw targetException; } } /** * Gets the actual client proxy. This implementation ensures only one * client proxy is ever created, even in multi-threaded situations * * @return service client proxy * @throws MalformedURLException */ private synchronized Object getClient() throws Exception { if (_serviceClient == null) { _serviceClient = createClient(); } return _serviceClient; } } public List getFaultHandlers() { return faultHandlers; } public void setFaultHandlers(List faultHandlers) { this.faultHandlers = faultHandlers; } public List getInHandlers() { return inHandlers; } public void setInHandlers(List inHandlers) { this.inHandlers = inHandlers; } public List getOutHandlers() { return outHandlers; } public void setOutHandlers(List outHandlers) { this.outHandlers = outHandlers; } }