As I was developing Stubby (a Lotus Notes database that helps you create Apache Axis "stub" files that can be used to call web services from Lotus Notes 7.x), I learned a few things about using Axis client stubs. Nothing earth-shattering, but a few tips that might help out other people looking to do similar things.
PLEASE NOTE that this is based on my use of WSDL2Java generated stubs from semi-early versions of Axis 1.x (like 1.1 and 1.2). Some of the problems I describe might have been fixed in later versions, and some of the techniques might either not work properly or might be done differently in newer versions of Axis. In other words, as with everything in life, YMMV.
Let's start with the basic example of a web service that provides stock market information. Our imaginary web service will have a method that allows you to send up a stock ticker symbol and will return the current price of that stock. The SOAP envelope to make the call might look something like this:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Body> <ns1:GetStockInfo xmlns:ns1="urn:thisNamespace"> <ns1:symbol>FOO</ns1:symbol> </ns1:GetStockInfo> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
If you use Apache Axis and WSDL2Java to generate Axis stub files (here are some quick instructions if you've never used WSDL2Java before), you'll be able to write Java client code like this to generate that type of envelope and call the web service:
import StockInfoNamespace.*; StockInfoServiceLocator locator = new StockInfoServiceLocator(); StockInfoService service = locator.getStockService(); service.GetStockInfo("FOO");
Pretty straightforward once you get everything working. So what are some of the "extra things" you'll want to do...?
Use a SOAPAction HTTP Header
If the web service requires a SOAPAction HTTP Header, it will be defined in the WSDL file and the Axis client stub code should generate a SOAPAction header for you when the request is sent. However, some versions of Axis will generate a header called "HTTP_SOAPAction", while the web service itself might be looking for a header called just "SOAPAction". In this case, the server will reject the SOAP message and the web service call will fail.
So, what do you do? One answer (probably not the only one, but it works for me) is to edit the __SoapBindingStub.java file that was created and add the following method:
private void fixSoapAction (org.apache.axis.client.Call _call) { _call.setProperty( org.apache.axis.client.Call.SESSION_MAINTAIN_PROPERTY, new Boolean(true)); _call.setProperty( org.apache.axis.transport.http.HTTPConstants.HEADER_COOKIE2, "\r\nSOAPAction: " + _call.getSOAPActionURI()); }
And then on all the method calls in the SoapBindingStub.java file, just after the line _call.setSOAPActionURI(), add the line:
fixSoapAction(_call);
What you're doing there is leaving the HTTP_SOAPAction header in place, and then adding a new HTTP Header called "SOAPAction" with the same value. You do this by adding a linefeed (\r\n) to the end of the "Cookie2" HTTP Header to create a new Header element, and then assign it a value equal to the existing SOAPActionURI.
Notice that you also have to set Call.SESSION_MAINTAIN_PROPERTY to a Boolean "true". If that property is not true, the Cookie Header value will never be set, and neither will your new SOAPAction Header.
Using Basic HTTP Authentication
If your web service requires you to use HTTP Basic Authentication, you can adjust your Axis client code like so:
import StockInfoNamespace.*; import org.apache.axis.client.Stub; import org.apache.axis.client.Call; StockInfoServiceLocator locator = new StockInfoServiceLocator(); StockInfoService service = locator.getStockService(); // to use Basic HTTP Authentication: ((Stub) service)._setProperty(Call.USERNAME_PROPERTY, "user name"); ((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, "password"); service.GetStockInfo("FOO");
Using Session-Based Authentication
If your web service requires you to use session-based Authentication (which requires you to pass a token as an HTTP Cookie), you can adjust your Axis client code like so:
import StockInfoNamespace.*; import org.apache.axis.client.Stub; import org.apache.axis.client.Call; import org.apache.axis.transport.http.HTTPConstants; StockInfoServiceLocator locator = new StockInfoServiceLocator(); StockInfoService service = locator.getStockService(); // to set a cookie: ((Stub) service)._setProperty(Call.SESSION_MAINTAIN_PROPERTY, new Boolean(true)); ((Stub) service)._setProperty(HTTPConstants.HEADER_COOKIE, "AuthToken=abc123"); service.GetStockInfo("FOO");
How you actually get the cookie/token depends on the system you're working with. Please Read The Fabulous Manual for your system to figure that one out.
Notice that you also have to set Call.SESSION_MAINTAIN_PROPERTY to a Boolean "true". If that property is not true, the Cookie value will not be sent.
Adding SOAP Header Elements
Sometimes a web service will also require you to include SOAP Header information, usually for login purposes. In the case of explicit headers (that are defined in the WSDL), the SOAP Header information is supposed to be writable directly from the Axis client code. However, if the headers are implicit (not defined in the WSDL), you will have to generate them manually.
There are two kinds of header elements you may have to generate. The first are elements that are simply child nodes of the <Header> element, like so:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Header> <ns1:UserName xmlns:ns1="urn:thisNamespace">John Doe</ns1:UserName> </SOAP-ENV:Header> <SOAP-ENV:Body> <ns2:GetStockInfo xmlns:ns2="urn:thisNamespace"> <ns2:symbol>FOO</ns2:symbol> </ns2:GetStockInfo> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
This type of envelope can be created with client code similar to this:
import StockInfoNamespace.*; import org.apache.axis.client.Stub; StockInfoServiceLocator locator = new StockInfoServiceLocator(); StockInfoService service = locator.getStockService(); // add a <UserName> node to the SOAP Header ((Stub) service).setHeader("urn:thisNamespace", "UserName", "John Doe"); service.GetStockInfo("FOO");
You can also end up with header elements that are nested, such that there are nodes with subnodes within the <Header> element, like so:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> <SOAP-ENV:Header> <ns1:AuthenticationInfo xmlns:ns1="urn:thisNamespace"> <ns1:UserName>John Doe</ns1:UserName> </ns1:AuthenticationInfo> </SOAP-ENV:Header> <SOAP-ENV:Body> <ns2:GetStockInfo xmlns:ns2="urn:thisNamespace"> <ns2:symbol>FOO</ns2:symbol> </ns2:GetStockInfo> </SOAP-ENV:Body> </SOAP-ENV:Envelope>
In this case, the <UserName> node is within an <AuthenticationInfo> node within the SOAP Header. This type of envelope can be created with client code similar to this:
import StockInfoNamespace.*; import org.apache.axis.client.Stub; import org.apache.axis.message.SOAPHeaderElement; import javax.xml.soap.SOAPElement; StockInfoServiceLocator locator = new StockInfoServiceLocator(); StockInfoService service = locator.getStockService(); // add an <AuthenticationInfo> node with a <UserName> subnode // to the SOAP Header SOAPHeaderElement header = new SOAPHeaderElement( "urn:thisNamespace", "AuthenticationInfo"); SOAPElement node = header.addChildElement("UserName"); node.addTextNode("John Doe"); ((Stub) service).setHeader(header); service.GetStockInfo("FOO");
Note that we're talking about SOAP Envelope Headers here, not HTTP headers. See the discussion on SOAPAction heders at the top of this page for information on adding custom HTTP Headers.
Use a custom client-config.wsdd at runtime
The Axis client gets certain settings from a configuration file called client-config.wsdd. Sometimes you want your client code to use custom client-config.wsdd settings, specified at runtime.
One way to do this is to edit the __ServiceLocator.java file that was generated by WSDL2Java and add the following method:
protected org.apache.axis.EngineConfiguration getEngineConfiguration() { java.lang.StringBuffer sb = new java.lang.StringBuffer(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"); sb.append("<deployment name=\"defaultClientConfig\"\r\n"); sb.append("xmlns=\"http://xml.apache.org/axis/wsdd/\"\r\n"); sb.append("xmlns:java=\"http://xml.apache.org/axis/wsdd/providers/java\">\r\n"); sb.append("<transport name=\"http\" pivot=\"java:org.apache.axis.transport.http.HTTPSender\" />\r\n"); sb.append("<transport name=\"local\" pivot=\"java:org.apache.axis.transport.local.LocalSender\" />\r\n"); sb.append("<transport name=\"java\" pivot=\"java:org.apache.axis.transport.java.JavaSender\" />\r\n"); sb.append("</deployment>\r\n"); org.apache.axis.configuration.XMLStringProvider config = new org.apache.axis.configuration.XMLStringProvider(sb.toString()); return config; }
This code will cause the client to use the specified configuration information above instead of whatever default client-config.wsdd file you have. Note that there are only certain cases where you want to do this, though. Normally you're better off editing the client-config.wsdd file directly.
You should also be able to generate the EngineConfiguration object in your client code (as in the method above) and pass it directly to the __ServiceLocator class when you're instantiating it. For example:
org.apache.axis.EngineConfiguration myEngine = [see getEngineConfiguration above...] MyServiceLocator locator = new MyServiceLocator(myEngine);
Using NTLM Authentication in an Axis web service client
I haven't tried doing this, but apparently you can have your Axis client use Windows NTLM authentication by telling the client-config.wsdd file to use "CommonsHTTPSender" instead of just plain "HTTPSender" as the http transport. Do a Google search for "NTLM and CommonsHTTPSender" for more detail.
Using WSS4J Authentication in an Axis web service client
Using WSS4J to pass WS-Security info is yet another thing I haven't tried, but Johan Danforth has some information on his blog:
http://weblogs.asp.net/jdanforth/archive/2005/01/22/358764.aspx
http://weblogs.asp.net/jdanforth/archive/2005/01/16/354060.aspx