/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 2001 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Axis" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.axis.client ;
import java.util.* ;
import java.net.*;
import org.apache.axis.* ;
import org.apache.axis.configuration.FileProvider;
import org.apache.axis.encoding.DeserializerFactory;
import org.apache.axis.encoding.Serializer;
import org.apache.axis.encoding.ServiceDescription;
import org.apache.axis.encoding.TypeMappingRegistry;
import org.apache.axis.handlers.* ;
import org.apache.axis.message.*;
import org.apache.axis.registries.HandlerRegistry;
import org.apache.axis.transport.http.HTTPConstants;
import org.apache.axis.transport.http.HTTPSender;
import org.apache.axis.transport.http.HTTPTransport;
import org.apache.axis.utils.* ;
import org.w3c.dom.* ;
import java.io.*;
import org.apache.axis.encoding.SerializationContext;
import org.apache.log4j.Category;
/**
* Allows an Axis service to be invoked from the client side.
* Contains message and session state which may be reused
* across multiple invocations on the same ServiceClient.
*
* @author Rob Jellinghaus (robj@unrealities.com)
* @author Doug Davis (dug@us.ibm.com)
* @author Glen Daniels (gdaniels@macromedia.com)
*/
// Need to add proxy, ssl.... other cool things - but it's a start
// Only supports String
public class ServiceClient {
private static final boolean DEBUG_LOG = false;
static Category category =
Category.getInstance(ServiceClient.class.getName());
/***************************************************************
* Static stuff
*/
public static final String TRANSPORT_PROPERTY =
"java.protocol.handler.pkgs";
/**
* A Hashtable mapping protocols (Strings) to Transports (classes)
*/
private static Hashtable transports = new Hashtable();
private static boolean initialized = false;
private static FileProvider configProvider = new FileProvider("client-config.xml");
/** Register a Transport that should be used for URLs of the specified
* protocol.
*
* @param protocol the URL protocol (i.e. "tcp" for "tcp://" urls)
* @param transportClass the class of a Transport type which will be used
* for matching URLs.
*/
public static void setTransportForProtocol(String protocol,
Class transportClass)
{
if (Transport.class.isAssignableFrom(transportClass))
transports.put(protocol, transportClass);
else
throw new NullPointerException();
}
/**
* Set up the default transport URL mappings.
*
* This must be called BEFORE doing non-standard URL parsing (i.e. if you
* want the system to accept a "local:" URL). This is why the Options class
* calls it before parsing the command-line URL argument.
*/
public static synchronized void initialize()
{
if (!initialized) {
addTransportPackage("org.apache.axis.transport");
setTransportForProtocol("local", org.apache.axis.transport.local.LocalTransport.class);
setTransportForProtocol("http", HTTPTransport.class);
setTransportForProtocol("https", HTTPTransport.class);
initialized = true;
}
}
/** Add a package to the system protocol handler search path. This
* enables users to create their own URLStreamHandler classes, and thus
* allow custom protocols to be used in Axis (typically on the client
* command line).
*
* For instance, if you add "samples.transport" to the packages property,
* and have a class samples.transport.tcp.Handler, the system will be able
* to parse URLs of the form "tcp://host:port..."
*
* @param packageName the package in which to search for protocol names.
*/
public static synchronized void addTransportPackage(String packageName)
{
String currentPackages = System.getProperty(TRANSPORT_PROPERTY);
if (currentPackages == null) {
currentPackages = "";
} else {
currentPackages += "|";
}
currentPackages += packageName;
System.setProperty(TRANSPORT_PROPERTY, currentPackages);
}
/*****************************************************************************
* END STATICS
*/
// Our AxisClient
private AxisEngine engine;
// The description of our service
private ServiceDescription serviceDesc;
// The message context we use across invocations
private MessageContext msgContext;
// Collection of properties to store and put in MessageContext at
// invoke() time
private Hashtable myProperties = null;
private int timeout;
private boolean maintainSession;
// Our Transport, if any
private Transport transport;
private String transportName;
// A place to store output parameters
private Vector outParams = null;
/**
* Basic, no-argument constructor.
*/
public ServiceClient () {
this(new AxisClient(configProvider));
}
/**
* Construct a ServiceClient with just an AxisEngine.
*/
public ServiceClient (AxisEngine engine) {
this.engine = engine;
msgContext = new MessageContext(engine);
if (!initialized)
initialize();
}
/**
* Construct a ServiceClient with a given endpoint URL
*
* @param endpointURL a string containing the transport endpoint for this
* service.
*/
public ServiceClient(String endpointURL)
throws AxisFault
{
this(endpointURL, new AxisClient(configProvider));
}
/**
* Construct a ServiceClient with a given endpoint URL & engine
*/
public ServiceClient(String endpointURL, AxisEngine engine)
throws AxisFault
{
this(engine);
this.setURL(endpointURL);
}
/**
* Construct a ServiceClient with the given Transport.
*
* @param transport a pre-constructed Transport object which will be used
* to set up the MessageContext appropriately for each
* request
*/
public ServiceClient (Transport transport) {
this(transport, new AxisClient(configProvider));
}
/**
* Construct a ServiceClient with the given Transport & engine.
*/
public ServiceClient (Transport transport, AxisEngine engine) {
this(engine);
setTransport(transport);
}
/**
* Set the Transport for this ServiceClient.
*
* @param transport the Transport object we'll use to set up
* MessageContext properties.
*/
public void setTransport(Transport transport) {
this.transport = transport;
if (category.isInfoEnabled())
category.info("Transport is " + transport);
}
/**
* Set the URL (and the transport state).
*/
public void setURL (String endpointURL)
throws AxisFault
{
try {
URL url = new URL(endpointURL);
String protocol = url.getProtocol();
Transport transport = getTransportForProtocol(protocol);
if (transport == null)
throw new AxisFault("ServiceClient.setURL",
"No transport mapping for protocol: " + protocol,
null, null);
transport.setUrl(endpointURL);
setTransport(transport);
} catch (MalformedURLException e) {
throw new AxisFault("ServiceClient.setURL",
"Malformed URL Exception: " + e.getMessage(), null, null);
}
}
/**
* Set the name of the transport chain to use.
*/
public void setTransportName(String name) {
transportName = name ;
if ( transport != null )
transport.setTransportName( name );
}
/** Get the Transport registered for the given protocol.
*
* @param protocol a protocol such as "http" or "local" which may
* have a Transport object associated with it.
* @return the Transport registered for this protocol, or null if none.
*/
public Transport getTransportForProtocol(String protocol)
{
Class transportClass = (Class)transports.get(protocol);
Transport ret = null;
if (transportClass != null) {
try {
ret = (Transport)transportClass.newInstance();
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
}
return ret;
}
/**
* Set property in our MessageContext.
*
* @param name the property name to set.
* @param value the value of the property.
*/
public void set (String name, Object value) {
if (name == null || value == null)
return;
if (myProperties == null) {
myProperties = new Hashtable();
}
myProperties.put(name, value);
}
/**
* Get a property from our MessageContext.
*
* @param name the property name to retrieve.
* @return the property's value.
*/
public Object get (String name) {
return (name == null || myProperties == null) ? null :
myProperties.get(name);
}
/**
* Set timeout in our MessageContext.
*
* @param value the maximum amount of time, in milliseconds
*/
public void setTimeout (int value) {
timeout = value;
}
/**
* Get timeout from our MessageContext.
*
* @return value the maximum amount of time, in milliseconds
*/
public int getTimeout () {
return timeout;
}
/**
* Directly set the request message in our MessageContext.
*
* This allows custom message creation.
*
* @param msg the new request message.
*/
public void setRequestMessage(Message msg) {
msgContext.setRequestMessage(msg);
}
/**
* Directly get the response message in our MessageContext.
*
* Shortcut for having to go thru the msgContext
*
* @return the response Message object in the msgContext
*/
public Message getResponseMessage() {
return msgContext.getResponseMessage();
}
/**
* Determine whether we'd like to track sessions or not.
*
* This just passes through the value into the MessageContext.
*
* @param yesno true if session state is desired, false if not.
*/
public void setMaintainSession (boolean yesno) {
maintainSession = yesno;
}
/**
* Obtain a reference to our MessageContext.
*
* @return the ServiceClient's MessageContext.
*/
public MessageContext getMessageContext () {
return msgContext;
}
/**
* Set the ServiceDescription associated with this ServiceClient.
*
* @param serviceDesc a ServiceDescription.
*/
public void setServiceDescription(ServiceDescription serviceDesc)
{
this.serviceDesc = serviceDesc;
}
/**
* Get the output parameters (if any) from the last invocation.
*
* NOTE that the params returned are all RPCParams, containing
* name and value - if you want the value, you'll need to call
* param.getValue().
*
* @return a Vector of RPCParams
*/
public Vector getOutputParams()
{
return this.outParams;
}
/**
* Map a type for serialization.
*
* @param _class the Java class of the data type.
* @param qName the xsi:type QName of the associated XML type.
* @param serializer a Serializer which will be used to write the XML.
*/
public void addSerializer(Class _class, QName qName, Serializer serializer) {
TypeMappingRegistry typeMap = msgContext.getTypeMappingRegistry();
typeMap.addSerializer(_class, qName, serializer);
}
/**
* Map a type for deserialization.
*
* @param qName the xsi:type QName of an XML Schema type.
* @param _class the class of the associated Java data type.
* @param deserializerFactory a factory which can create deserializer
* instances for this type.
*/
public void addDeserializerFactory(QName qName, Class _class,
DeserializerFactory deserializerFactory) {
TypeMappingRegistry typeMap = msgContext.getTypeMappingRegistry();
typeMap.addDeserializerFactory(qName, _class, deserializerFactory);
}
/************************************************
* Invocation
*/
/** Invoke the service with a custom SOAPEnvelope.
*
* @param env a SOAPEnvelope to send.
* @exception AxisFault
*/
public SOAPEnvelope invoke(SOAPEnvelope env) throws AxisFault
{
msgContext.reset();
msgContext.setRequestMessage(new Message(env));
invoke();
return msgContext.getResponseMessage().getAsSOAPEnvelope();
}
/** Invoke an RPC service with a method name and arguments.
*
* This will call the service, serializing all the arguments, and
* then deserialize the return value.
*
* @param namespace the desired namespace URI of the method element
* @param method the method name
* @param args an array of Objects representing the arguments to the
* invoked method. If any of these objects are RPCParams,
* Axis will use the embedded name of the RPCParam as the
* name of the parameter. Otherwise, we will serialize
* each argument as an XML element called "arg<n>".
* @return a deserialized Java Object containing the return value
* @exception AxisFault
*/
public Object invoke( String namespace, String method, Object[] args ) throws AxisFault {
category.debug("Enter: ServiceClient::invoke(ns, meth, args)" );
RPCElement body = new RPCElement(namespace, method, args, serviceDesc);
Object ret = invoke( body );
category.debug("Exit: ServiceClient::invoke(ns, meth, args)" );
return ret;
}
/** Convenience method to invoke a method with a default (empty)
* namespace. Calls invoke() above.
*
* @param method the method name
* @param args an array of Objects representing the arguments to the
* invoked method. If any of these objects are RPCParams,
* Axis will use the embedded name of the RPCParam as the
* name of the parameter. Otherwise, we will serialize
* each argument as an XML element called "arg<n>".
* @return a deserialized Java Object containing the return value
* @exception AxisFault
*/
public Object invoke( String method, Object [] args ) throws AxisFault
{
return invoke("", method, args);
}
/** Invoke an RPC service with a pre-constructed RPCElement.
*
* @param body an RPCElement containing all the information about
* this call.
* @return a deserialized Java Object containing the return value
* @exception AxisFault
*/
public Object invoke( RPCElement body ) throws AxisFault {
category.debug("Enter: ServiceClient::invoke(RPCElement)" );
SOAPEnvelope reqEnv = new SOAPEnvelope();
SOAPEnvelope resEnv = null ;
Message reqMsg = new Message( reqEnv );
Message resMsg = null ;
Vector resArgs = null ;
Object result = null ;
// Clear the output params
outParams = null;
String uri = null;
if (serviceDesc != null) uri = serviceDesc.getEncodingStyleURI();
if (uri != null) reqEnv.setEncodingStyleURI(uri);
msgContext.setRequestMessage(reqMsg);
msgContext.setResponseMessage(resMsg);
reqEnv.addBodyElement(body);
reqEnv.setMessageType(ServiceDescription.REQUEST);
if ( body.getPrefix() == null ) body.setPrefix( "m" );
if ( body.getNamespaceURI() == null ) {
throw new AxisFault("ServiceClient.invoke", "Cannot invoke ServiceClient with null namespace URI for method "+body.getMethodName(),
null, null);
} else if (msgContext.getServiceHandler() == null) {
msgContext.setTargetService(body.getNamespaceURI());
}
if (DEBUG_LOG) {
try {
SerializationContext ctx = new SerializationContext(new PrintWriter(System.out), msgContext);
System.out.println("");
System.out.println("**DEBUG**");
reqEnv.output(ctx);
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
} finally {
System.out.println("");
System.out.println("**DEBUG**");
System.out.println("");
}
}
try {
invoke();
}
catch( Exception e ) {
category.error( e );
if ( !(e instanceof AxisFault ) ) e = new AxisFault( e );
throw (AxisFault) e ;
}
resMsg = msgContext.getResponseMessage();
if (resMsg == null)
throw new AxisFault(new Exception("Null response message!"));
/** This must happen before deserialization...
*/
resMsg.setMessageType(ServiceDescription.RESPONSE);
resEnv = (SOAPEnvelope)resMsg.getAsSOAPEnvelope();
SOAPBodyElement respBody = resEnv.getFirstBody();
if (respBody instanceof SOAPFaultElement) {
throw ((SOAPFaultElement)respBody).getAxisFault();
}
body = (RPCElement)resEnv.getFirstBody();
resArgs = body.getParams();
if (resArgs != null && resArgs.size() > 0) {
RPCParam param = (RPCParam)resArgs.get(0);
result = param.getValue();
/**
* Are there out-params? If so, return a Vector instead.
*/
if (resArgs.size() > 1) {
outParams = new Vector();
for (int i = 1; i < resArgs.size(); i++) {
outParams.add(resArgs.get(i));
}
}
}
category.debug("Exit: ServiceClient::invoke(RPCElement)" );
return( result );
}
/**
* Set engine option.
*/
public void addOption(String name, Object value) {
engine.addOption(name, value);
}
/**
* Invoke this ServiceClient with its established MessageContext
* (perhaps because you called this.setRequestMessage())
*
* @exception AxisFault
*/
public void invoke() throws AxisFault {
category.debug("Enter: Service::invoke()" );
msgContext.reset();
msgContext.setTimeout(timeout);
if (myProperties != null) {
Enumeration enum = myProperties.keys();
while (enum.hasMoreElements()) {
String name = (String) enum.nextElement();
Object value = myProperties.get(name);
msgContext.setProperty(name, value);
}
}
msgContext.setServiceDescription(serviceDesc);
msgContext.setMaintainSession(maintainSession);
// set up message context if there is a transport
if (transport != null) {
transport.setupMessageContext(msgContext, this, this.engine);
}
else
msgContext.setTransportName( transportName );
try {
engine.invoke( msgContext );
if (transport != null)
transport.processReturnedMessageContext(msgContext);
}
catch( AxisFault fault ) {
category.error( fault );
throw fault ;
}
category.debug("Exit: Service::invoke()" );
}
}