As a first step into the web service world let's take one of the most common web service examples used: a StockQuote service. For this purpose I will cheat and have our service just return a price of US$55.55 for all stock symbols passed in. So, let's say our web service looks like Listing 1.
Listing 1. StockQuote service
public class StockQuoteService {
public float getQuote(String symbol) {
return( 55.55 );
}
}
|
The first thing you might notice is that the code doesn't really look like anything new or special -- where's the "web service" part of it? In simple RPC cases there isn't anything special -- that's one of the nice things about web services, you should be able to take existing Java code and turn it into a web service with little effort.
Now that you have some code, that you can claim is a web service, the next step is to make it available for someone to use -- or deploy it.
Deploying a service to Axis requires two steps: first you must make sure that the Java class is available to the Axis runtime engine, and second you must make Axis aware of your service. Since Axis is a servlet, placing your class files in your webapp's WEB-INF/classes directory will make them available to the Axis servlet. Making Axis aware of your service is a little harder. Here you have two options:
- Modify the server side configuration file --The Axis server uses a file named server-config.wsdd to store all of its persistence information -- things like the list of services deployed. This file can be found in the webapp's WEB-INF directory. So, in this case you can manually modify this file and add the XML shown in Listing 2.
Listing 2. XML added to server-config.wsdd
<service name="StockQuoteService" provider="java:RPC"> <parameter name="className" value="StockQuoteService"/> <parameter name="allowedMethods" value="*"/> </service> |
Most of what this XML is doing should be fairly obvious; you're defining a new service called "StockQuoteService" and telling Axis that it's an RPC service. The "provider" attribute tells Axis which Dispatcher to use -- in this case, the Java RPC one. There are two parameters that will be used by the provider, one tells it the Java classname of the service and the other one defines which methods are exposed as services (in this case, all of them).
Once you modify the server-config.wsdd file you'll need to restart Axis, or more specifically the application server, in order for these changes to be noticed.
This method of deploying services isn't very good for cases where you want to dynamically (at runtime) change the available services, however, if you want to predeploy services before the application server starts then this is the way to go.
- Dynamically (remotely) deploy your service -- In this situation you tell Axis about your new service without manually editing the server side configuration file or even restarting Axis. To do this you invoke an administrative web service running inside Axis. In short, you take any new services (or even handlers) and place them in a WSDD file and give it to the AdminService. So, for this scenario you create a WSDD file called stock.wsdd, as shown in Listing 3.
Listing 3. stock.wsdd
<deployment name="test" xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="StockQuoteService" provider="java:RPC">
<parameter name="className" value="StockQuoteService"/>
<parameter name="allowedMethods" value="*"/>
</service>
</deployment>
|
Notice that the WSDD looks just like it did for the static deployment, except it is wrapped in a <deployment> tag. Now all that's left is to invoke the AdminService, as shown in Listing 4.
Listing 4. Invoke AdminService
java org.apache.axis.client.AdminClient -l http://localhost:8080/wstk/services/AdminService stock.wsdd |
This Java command will run the AdminClient -- basically just a simple tool that takes the wsdd file passed in on the command line (stock.wsdd in this example) and sends it to the AdminService (which by the way is an example of a Message service). The -l (el) option tells it where the AdminService is running -- in this case you have it deployed as a service in the ETTK's environment.
No matter how you choose to deploy your service you can verify that it worked by using the AdminClient's "list" feature, as shown in Listing 5.
Listing 5. AdminClient's "list" feature
java org.apache.axis.client.AdminClient -l http://localhost:8080/wstk/services/AdminService list |
This will return the complete list of services and handlers currently deployed in the Axis server. Now that the service is deployed you need to try to invoke it.
There are three different ways to invoke RPC services in Axis:
- Dynamic Invocation Interface without WSDL -- The standard Java interface for accessing web services (also known as JAX-RPC) has a dynamic invocation interface (DII) that allows you to access RPC services with and without using the WSDL that defines the service. First you can do it without using the WSDL, as shown in Listing 6.
Listing 6. Dynamic Invocation Interface without WSDL
import java.net.*;
import org.apache.axis.client.* ;
import javax.xml.namespace.QName;
public class getQuote1 {
public static void main(String[] args) throws Exception {
String url = "http://localhost:8080/wstk/services/StockQuoteService";
Service service = new Service();
Call call = (Call) service.createCall();
call.setTargetEndpointAddress( new URL(url) );
call.setUseSOAPAction( true );
call.setSOAPActionURI( "getQuote" );
call.setEncodingStyle( "http://schemas.xmlsoap.org/soap/encoding/" );
call.setOperationName( new QName(url, "getQuote") );
call.addParameter( "symbol", XMLType.XSD_STRING, ParameterMode.IN );
call.setReturnType( XMLType.XSD_FLOAT );
Object result = call.invoke( new Object[] {args[0]} );
System.out.println(args[0] + ": " + result );
}
}
|
Stepping through the code you can see that you create a Service and Call objects -- once you have the Call object you then have to define some basic SOAP properties: the URL of where the service is deployed, the value of the SOAPAction HTTP header, the type of encoding to use, the QName of the operation you want to invoke, the types of parameters you'll pass to this operation, and finally the result value's type. Once all of that information is defined you then invoke the service passing in the symbol that was given to you on the command line. Notice that the parameters to the operation are passed in as an array of Java objects rather than as individual parameters to the invoke() method.
So, when you run it you see the output shown in Listing 7.
Listing 7. Output from running code in Listing 6
> java getQuote1 XXX XXX: 55.25 |
This sample, while fairly short, has a lot of steps that could get very tedious over time, for example the "addParameter" method which tells Axis the name of the parameter to the method, and its type. Instead, you can have Axis determine most of that information by giving it the WSDL document for the service -- which leads to the next way of invoking RPC services.
- Dynamic Invocation Interface with WSDL -- This is basically the same as above except here you pass in the URL to the WSDL document, specify which specific service, port and operation you want, and Axis will figure out the other auxiliary information (see Listing 8).
Listing 8. Dynamic Invocation Interface with WSDL
import java.net.*;
import org.apache.axis.client.* ;
import javax.xml.namespace.QName;
public class getQuote2 {
public static void main(String[] args) throws Exception {
String url = "http://localhost:8080/wstk/services/StockQuoteService?wsdl";
String namespace = "http://localhost:8080/wstk/services/StockQuoteService";
QName serviceQN = new QName( namespace, "StockQuoteServiceService" );
QName portQN = new QName( namespace, "StockQuoteService" );
Service service = new Service(new URL(url), serviceQN );
Call call = (Call) service.createCall( portQN, "getQuote" );
Object result = call.invoke( new Object[] {args[0]} );
System.out.println(args[0] + ": " + result );
}
}
|
Here, you see that the code is a little bit smaller, but more importantly a lot of the information that you had to manually define on the Call object is now hidden from you because Axis was able to extract it from the WSDL document. Another noteworthy thing is that the URL of the WSDL document is the URL of the service itself with a "?wsdl" at the end; here you're asking Axis to generate the WSDL for us.
- Proxy Classes -- The final way to invoke the service is through the use of a proxy class (also known as a Stub). Axis provides a tool that will take the WSDL document for a service and generate a client-side class that you can invoke that will hide all of the SOAPness from you. For our example if you run the code in Listing 9, it generates several Java files for you, but the most important one is called localhost/StockQuoteServiceLocator.java -- this class acts as a proxy class that does all of the SOAP work for you.
Listing 9. Generate proxy class
wsdl2java http://localhost:8080/wstk/services/StockQuoteService?wsdl |
So, your code would be reduce down to that shown in Listing 10.
Listing 10. Proxy class
import localhost.* ;
public class getQuote3 {
public static void main(String[] args) throws Exception {
StockQuoteServiceServiceLocator loc = new StockQuoteServiceServiceLocator();
localhost.StockQuoteService sqs = loc.getStockQuoteService();
System.out.println( args[0] + ": " + sqs.getQuote(args[0]) );
}
}
|
Here you use the StockQuoteService class just as if the service were a local Java class.
There is no clear rule for deciding which of the three types of invocation methods to use -- it will vary depending on your situation. Obviously, if you don't have WSDL then you can only use the first. If you want your code to be portable to other JAX-RPC implementations besides Axis then, you may want to use the DII interfaces (avoiding the Axis extensions). For more information about using Axis see the Axis User's Guide (see Resources for a link).
Unlike the RPC case, message services are required to match a certain method signature. In the easiest case the method should take in an array of DOM Elements (which will correspond to the SOAP Body elements) and return an array of DOM Elements (the resulting SOAP Body elements). So, for example, let's examine the BasicWS demo's Message service (see Listing 11).
Listing 11. BasicWS demo's Message service
import org.w3c.dom.* ;
public class MessageService {
public Element[] process(Element[] xmls) {
Element[] elems = new Element[xmls.length];
for ( int i = 0 ; i < elems.length ; i++ )
elems[i] = xmls[elems.length-i-1] ;
return elems;
}
}
|
So, unlike in the RPC case where Axis will convert the XML to and from Java programming language for you, in the Message case it leaves the XML untouched, and you can see you've defined a single method called "process" that takes an array of Elements and returns one as well. Just as a side note, this service will return the same array of Elements back to the client but in the reverse order.
Deploying a message service is basically the same as in the RPC case with just a couple of exceptions, let's look at the MessageService's <service> element from the WSDD file as an example (see Listing 12).
Listing 12. MessageService's <service> element
<service name="MessageService" provider="java:MSG"> <parameter name="className" value="basicWS.MessageService" /> <parameter name="allowedMethods" value="process" /> </service> |
First, notice the "provider" attribute says "java:MSG" indicating that this is a message service instead of an RPC service. Second is the "allowedMethods" parameter. In this case, by specifying a single method name you're telling Axis that all requests should be routed though this one method regardless of what is in the XML. If you specified more than one method name, then Axis will match the local part of the first body element with the method name to determine which method to invoke.
Invoking a message service is basically the same as invoking an RPC service except instead of passing in an array of Java objects that will be converted into XML, you pass in an array of XML chunks. So, looking at snippets of the Message demo's client, you have the code in Listing 13.
Listing 13. Message demo's client code
Vector xmls = new Vector();
String str1 = "The first shall be last";
String str2 = "The last shall be first";
SOAPBodyElement xml1, xml2 ;
...
xml1 = new SOAPBodyElement(new ByteArrayInputStream(str1.getBytes()));
xml2 = new SOAPBodyElement(new ByteArrayInputStream(str2.getBytes()));
...
xmls = (Vector) call.invoke( new Object[] {xml1, xml2} );
|
Stepping through the code you can see that you create two SOAPBodyElement objects by passing in an InputStream to the constructor -- the constructor also accepts a DOM Element object. Axis requires you to wrap your Elements in SOAPBodyElements so that it knows whether it should pass the XML untouched to the server or whether you want to convert the Java objects (Element in this case) into XML (not wrappering them will do an RPC call instead of a Message call).
Axis' interfaces for communicating with Message style services isn't standard -- while there are some standard interfaces out there (JAXM for example) Axis chose to take just pieces of them and integrate them into its JAX-RPC extensions. For more information about using Axis see the Axis User's Guide (see Resources for a link).
As mentioned in the previous article, Axis supports the notion of pluggable components, Handlers, that allow you to intercept an outgoing or incoming message for the purpose of performing some additional processing. This processing can be on the message itself or can be unrelated entirely to the message -- Axis places no restriction on what a Handler can do.
Axis supports the use of Handlers in both the client and server processing models. Because of Axis' symmetry of processing (see the Axis section in Part 1 of this series), the same Handlers can be used on either side of the wire -- by that I mean the Handler interfaces are the same and the APIs you use to get data (such as the message itself) is the same. This makes it very useful when creating a Handler that wants to process an outgoing message because that means it can be placed on both the outgoing side on the client as well as the response (also outgoing) side on the server.
Really the only difference between a client-side Handler and a server-side Handler is how it is deployed into the Axis environment. Let's take a look at how this is done.
On the client there are two ways to register or deploy Handlers: add the Handlers to your Call object or register them with the Axis engine. Listing 14 shows the first method.
Listing 14. Add handler to call object
call.setClientHandlers( requestHandler, responseHandler ); |
This will tell Axis to invoke requestHandler before the message is sent to the server and invoke responseHandler before control is returned back to your application.
The other way to have handlers invoked is by deploying them to the Axis engine -- the easiest way to do this is through the modification of the client-side configuration file (client-config.wsdd). Listing 15 shows a sample client-config.wsdd file.
Listing 15. Sample client-config.wsdd
<?xml version="1.0" encoding="UTF-8"?>
<deployment xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="StockQuoteService">
<requestFlow>
<handler type="java:com.mycom.RequestHandler"/>
</requestFlow>
<responseFlow>
<handler type="java:com.mycom.ResponseHandler"/>
</responseFlow>
</service>
<transport name="http"
pivot="java:com.ibm.wstk.axis.handlers.WSTKHTTPSender"/>
</deployment>
|
In this example you tell Axis that for the service named StockQuoteService it should invoke the RequestHandler before sending the message to the server and invoke the ResponseHandler before returning control back to the application. Conceptually, very similar to the setClientHandlers() method call. The other entry in the xml file tells Axis what handler/dispatch to use for the http transport. That's just a default setting that needs to be there. One thing to remember is that by creating your own client-config.wsdd file you are overriding the default one Axis uses, so the best thing for you to do is to take the one from the axis.jar file and modify it (by adding your element), that way you are sure to get all of the default Axis client-side configuration files.
One last thing to note about the client-side configuration file; it needs to be available to the Axis run-time through the classpath. Most developers will simply put it in the current working directory, but it can be placed elsewhere just as long as that location is in the classpath.
Deploying the handlers on the server-side is very similar. There's a server-config.wsdd file you can modify. For predeploying services most people will take the one from the axis.jar file, modify it, and then place a copy of it in their webapp's WEB-INF/classes directory. If you look at the ETTK's WEB-INF/classes directory you should see all of the predeployed services and handlers the Toolkit ships with.
Adding Handlers to your service on the server-side is very simple, looking at the wsdd file used to deploy the StockQuoteService back in the RPC Services section we had the code shown in Listing 16.
Listing 16. XML added to server-config.wsdd
<service name="StockQuoteService" provider="java:RPC"> <parameter name="className" value="StockQuoteService"/> <parameter name="allowedMethods" value="*"/> </service> |
So, adding request or response handlers is done the same way it was done in the client-config.wsdd -- the new wsdd file is shown in Listing 17.
Listing 17. New wsdd file
<deployment name="test" xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java">
<service name="StockQuoteService" provider="java:RPC">
<parameter name="className" value="StockQuoteService"/>
<parameter name="allowedMethods" value="*"/>
<requestFlow>
<handler type="java:com.mycom.RequestHandler"/>
</requestFlow>
<responseFlow>
<handler type="java:com.mycom.ResponseHandler"/>
</responseFlow>
</service>
</deployment>
|
Now this new wsdd file can be sent to the AdminService using the AdminClient tool, or you can add this new <service> section to your predeployed server-config.wsdd.
For more information about Handlers, including how to create them and more configuration information about them, see the Axis documentation (see Resources for a link). Also, please see the ETTK for more details about the examples mentioned in this article.
With this quick overview of creating RPC and Message style services, along with the rest of the ETTK, you should be able to develop your own web services. The next article in this series will explore the Grid and show how you can convert your web services into Grid services.
- Read the first installment of this article, Developing using the ETTK, Part 1. (developerWorks, May 2003)
- Download the ETTK from alphaWorks.
- You can see a live running install of the ETTK on developerWorks.
- The ETTK's Utility Services demo's documentation can found on developerWorks.
- There is more information about Axis at apache.org.
- Read the Axis Users's Guide.
- You can find the SOAP Spec at w3.org.
- You can find information about WS-I at ws-i.org.
- Details of JAX-RPC are available at the Java Sun web site.