Implementing Web Services

Now that we've covered servlets and web apps in detail, we'd like to return to the topic of web services. In the previous chapter, we introduced the concept of a web service as an extension (formalization) of the basic HTTP web transaction, using XML content for app-to-app communication instead of consumption by a web browser client. In that chapter, we showed how easy it is to invoke an RPC-style web service, using client-side classes generated from a WSDL description file. In this section, we'll show the other side of that equation and demonstrate how to implement and deploy a web service. The world of web services has been moving quickly and the APIs, buzzwords, and hype continue to proliferate. The appeal of this style of interapp communication using simple web protocols has, to some extent, been tarnished by the design-by-committee approach of every standards body and competitor adding features and layers to the web services concept. The truth is that web services are (were) simple and elegant when compared to more elaborate protocols largely because they did not support all of the same semanticsstate management, callbacks, transactions, authentication, and security. As these features are added, the complexity returns. We will not cover all aspects of web services in detail but instead focus on the original XML-RPC model that is appealing for a wide variety of simple apps. The future adoption or rejection of the bulk of web services standards will dictate how they affect us all in the coming years. In , we walked through generating and running the client side of a web service (the Temperature service hosted at xmethods.net). In this chapter, we'll build and deploy our own web service, a simple fa├žade called EchoService. We'll be using the JWSDP again and Tomcat as a web services container.

XML-RPC in Action

It's worth taking a moment to review what a web service really is. In this case, we are using the RPC-style web service, which means that both the client and server have well-defined API functions with known arguments and return types. That API is ultimately specified in a WSDL document that anyone can use to implement either side of the service. What's really at the heart of this kind of web service is the standardization of the way that the arguments and return values are marshaled (packed up) for their trip over the net, using XML. Most other kinds of RPC (such as Java RMI) use "native" protocols that may not be easy to implement in all languages. Web services use XML to send their data. Furthermore, the data is sent over the standard web protocol, HTTP, which means that it uses the same kind of request/response that your web browser does (and that we implemented in with our own little web server).

Installation and Environment

In , we asked that you download and install JWSDP 1.5 from Sun and suggested that you might first want to install the Sun Tomcat reference web services container (in anticipation of using it in this chapter). If you haven't done one or both of those yet, please download the Tomcat container[*] now from http://java.oracle.com/webservices/containers/tomcat_for_JWSDP_1_5.html and unpack it in a location of your choice. If you haven't installed the JWSDP yet, please do so now, downloading it from http://java.oracle.com/webservices/jwsdp. The JWSDP installation program should ask you if you want to install components to the Tomcat server, and you should do so. If you previously installed JWSDP, you can install the components to Tomcat now by running the script installed in the directory where you installed JWSDP under JWSDP_HOME/jwsdp-shared/bin/jwsdpontomcat.sh (or .bat) and passing the location of your Tomcat installation as an argument.

[*] Unfortunately, at this writing only Sun's version of the Tomcat container works properly with the JWSDP 1.5, so you will have to install the package specified here. Fortunately, both of these are easy installs and uninstalls.

As with our client-side example in , the actual coding of the example is very simple. A little complexity of the packaging and environment is the only difficulty in running these examples. Deploying a web service using JWSDP involves building a WAR, such as those we created earlier in this chapter. Before it can be deployed, however, the WAR requires an extra step that adds generated components to the archive. To simplify this and running of the client-side test, we'll do as we did in the last chapter and provide both a complete Ant build as well as command-line client-env scripts to help you. (Building the WAR does not require any special environment, but Ant makes that part easier.) As always, the full example source and these build files can be found on the CD accompanying the tutorial or online at http://www.oracle.com/catalog/learnjava3/.

Defining the Service

To build our client-side API in , we began by downloading the WSDL description file for the (existing) Temperature service. The WSDL, again, is an XML file that describes the functions (methods) of the service and the types of arguments and return values they use. From this description, the wscompile command was able to generate the client-side classes that we needed to invoke the service remotely from Java. In creating our own web service, we have (at least) two choices. We could follow a similar process and write a WSDL document describing our service, using it to generate the necessary server-side framework. However, we can take an easier path. The wscompile command is capable of reading a standard Java interface and automatically generating both the WSDL and the server-side framework for us. This means that all we really have to do is write a normal Java interface that describes the methods we want to expose, implement that interface in a regular Java object, and then run the tools appropriately. JWSDP and Tomcat will do the rest. When all goes well, it's really quite easy.

Our Echo Service

We'll begin by doing just what we described earlier, writing both an interface and implementation for our EchoService. The first requirement is that the interface must extend the java.rmi.Remote interface. Although we're not technically doing Java RMI here, this interface still serves as a flag for the "remote" nature of the service.[*] As with Java RMI, this necessitates declaring that all of the methods of the interface can throw java.rmi.RemoteException as well.

[*] The same interface is used in Enterprise JavaBeans to identify the remote interface of the beans.

We'll create a simple service that echoes a few different kinds of values: an int, a String, and one of our own object types (a data holder object), MyObject. In the next section, we'll examine the data types, MyObject, and how they are handled in more detail. Here is the interface:

 package learningjava;
  
 public interface EchoService extends java.rmi.Remote
 {
 public int echoInt( int value )
 throws java.rmi.RemoteException;
 public String echoString( String value )
 throws java.rmi.RemoteException;
 public MyObject echoMyObject( MyObject value)
 throws java.rmi.RemoteException;
 }


We've named our echo methods individually to differentiate them because JAX-RPC doesn't handle overloaded methods. (If we'd had a name collision, JAX-RPC would have "numbered" our methods to distinguish them.) Here is the implementation:

 package learningjava;
  
 public class EchoServiceImpl implements EchoService
 {
 public int echoInt( int value ) { return value; }
 public String echoString( String value ) { return value; }
 public MyObject echoMyObject( MyObject value ) { return value; }
 }


We've placed these into a learningjava package because it will be easier to work with the tools that way. Note again that the EchoService interface extends Remote, indicating that its methods are to be exposed via the service. Our EchoServiceImpl implements this interface and does the obvious, just returning the same values.

Data Types

As you might guess, since the data for our service has to be expressed as XML in a standard way, there are some limitations to the type of objects that can be transferred. JAX-RPC supports most of the common Java data types and many standard classes directly. It can also decompose JavaBeans-compliant data classes composed of these standard types so that you can use your own classes.

Standard types

Table 15-1 summarizes the directly supported types (those types that map directly to W3C Schema types [see ] or for which JAX-RPC has a predefined mapping).

Table 15-1. Standard types

Category

Types

Primitives and their wrappers

boolean, Boolean, byte, Byte, short, Short, float, Float, int, Integer, long, Long, double, Double

Standard types

java.lang.String, java.math.BigDecimal, java.math.BigInteger, java.util.Calendar, java.util.Date

Collections

ArrayList, LinkedList, Vector, Stack, HashMap, treeMap, Hashtable, Properties, HashSet, treeSet


Standard Java arrays of all these types are also supported.

Value data objects

As we said, JAX-RPC can also work with our own object types, although there are several requirements and a caveat to mention. First, to be able to be marshaled, our objects must contain only fields that are supported data types (or further compositions of those). Next, our objects must follow two JavaBeans design patterns. It must have a public, no-args constructor and, if it contains any nonpublic fields, they must have "getter" and "setter" accessor methods. provides more details about these issues. Finally, unlike Java RMI, the simple way we're using XML-RPC here does not support the "behavior" or the real identity of our domain objects from end to end. When a Java client uses our WSDL document to generate implementation classes, they will be getting simple data-holder replicas of the classes we specify. These "value objects" will pass along all the data content of our objects but are not related to the originals in any other way. Our server-side implementation will, of course, receive the data in the form of our own "real" domain objects. That is why they need to have available constructors so that the server-side framework can create and populate them for us to consume. Here is our MyObject data holder class:

 package learningjava;
  
 public class MyObject
 {
 int intValue;
 String stringValue;
  
 public MyObject( ) { }
  
 public MyObject( int i, String s ) {
 this.intValue = i;
 this.stringValue = s;
 }
  
 public int getIntValue( ) { return intValue; }
  
 public void setIntValue( int intValue ) { this.intValue = intValue; }
  
 public String getStringValue( ) {
 return stringValue;
 }
 public void setStringValue( String stringValue ) {
 this.stringValue = stringValue;
 }
 }


This class is a simple data holder that contains an int and String value. We've followed the rules, supplying get/set methods for our two variables and a default no-args constructor.

Deploying the Web Service

Let's get down to business (or open up for business, as it were) and deploy our web service to Tomcat. As we did in , we'll walk through the steps here as if you were doing all of this on the command line. But, of course, we've supplied an Ant build that will both generate the completed WAR and also run the client-side test class. We'll discuss the Ant setup at the end of this section. The first step is to compile our app classes: EchoService.java, EchoServiceImpl.java, and MyObject.java. The wscompile tool reads our interface directly from the compiled class binary. Next, for the client-side code generation, the packaging tool requires a small configuration file to tell it some things about the deployment. A file called jaxrpc-ri.xml (supplied with the examples) includes the following lines of XML:

 // File: jaxrpc-ri.xml
 <?xml version="1.0" encoding="UTF-8"?>
 <webServices
 xmlns="http://java.oracle.com/xml/ns/jax-rpc/ri/dd" version="1.0"
 targetNamespaceBase="http://learningjava.com/wsdl"
 typeNamespaceBase="http://learningjava.com/types"
 urlPatternBase="/ws">
  
 <endpoint
 
 display 
 description="Example Echo Service"
 interface="learningjava.EchoService"
 implementation="learningjava.EchoServiceImpl"
 />
  
 <endpointMapping endpoint urlPattern="/echo"/>
  
 </webServices>


As before, this file is not part of the web services standards but just a requirement of the JWSDP implementation tools. The webServices element tells the wsdeploy tool that we are going to use several things about our service. First, it defines namespaces to use for the WSDL document describing our service (see for more on namespaces). These define what are essentially unique prefixes for our data element names in the WSDL document. The endpoint declaration describes the service by name, gives a description, and most importantly, identifies our interface and implementation classes. Finally, the endpointMapping element specifies the server URL mapping for our web service when it is deployed. We'll be calling our WAR echo.war, so our service will appear under an /echo/echo path. As we'll see, you can hit this URL with your web browser to get information about the service, too. Next, we'll create the "portable WAR" that serves as input to the deployment tool. This WAR contains our classes and jaxrpc-ri.xml description file as well as a base web.xml file for the app. As before, we'll need the a web.xml file for our WAR. In this case, the file will have entries added to it by the deployment tool. In a more advanced app, you might use web.xml to configure other aspects of the web services deployment, such as J2EE features of the container. You can just use an "empty" one such as this (supplied with the example code):

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE web-app
 PUBLIC "-//Oracle//DTD Web app 2.3//EN"
 "http://java.oracle.com/j2ee/dtds/web-app_2_3.dtd">
 <web-app>
 </web-app>


Create the input WAR, placing the classes in the standard location and the jaxrpc-ri.xml file with web.xml (again, the Ant build will do this and the remaining steps for you). The WAR file should look like this:

 WEB-INF/web.xml
 WEB-INF/jaxrpc-ri.xml
 WEB-INF/classes/learningjava/
 WEB-INF/classes/learningjava/EchoService.class
 WEB-INF/classes/learningjava/EchoServiceImpl.class
 WEB-INF/classes/learningjava/MyObject.class


The next step will take the portable WAR as input and generate a deployable WAR as output. The resulting WAR contains additional generated server framework classes and the generated WSDL document for our service. Run the wsdeploy command, found in /jaxrpc/bin in the directory where you installed JWSDP under <JWSDP_HOME>/jaxrpc/bin, like this. Here's a sample command line:

 % wsdeploy.sh -classpath classes -o echo.war echo-input.war


The classes argument is the classpath for your compiled classes (it can't read them from the JAR for some reason). The -o specifies the output WAR filename and the final argument is the input WAR filename. If you wish the skip all that fun, you can build the completed WAR with the supplied Ant build.xml file simply by running Ant or invoking ant build-war:

 % ant build-war


And we're ready to go! If you look inside the generated echo.war file, you'll see many additional classes now and also the Echo.wsdl document that has been generated for us. Deploy this WAR just as you would any other, by dropping it into the webapps directory of the Tomcat container that we installed earlier (under Tomcat 5.x, copy it to tomcat50-jwsdp/webapps). Monitor the log files in the logs directory for any problems.

Using the Service

After deploying the WAR, drive your web browser to the service URL. If you are running the server on the same machine, the URL should be http://localhost:8080/echo/echo. You should see a description of the service similar to the one shown in Screenshot-1. This tells you that the service is active and gives you its configuration information. You can click on the WSDL link to download the WSDL description file that was generated for our service (contained in our WAR). The "Model" link provides a JWSDP specific description of the service in another XML format.

Screenshot-1. Web services description
Java ScreenShot

We can use the WSDL to test our service, just as we did in . To do this on the command line, create a client-config.xml file with the following lines (or use the one supplied with the examples):

 <configuration xmlns="http://java.oracle.com/xml/ns/jax-rpc/ri/config">
 <wsdl location="Echo.wsdl" packageName="learningjava.impl"/>
 </configuration>


Here, we've specified that the generated classes should go into a separate package, learningjava.impl, to avoid confusion between the generated MyObject and our original. Use the wscompile tool as we did before:

 % wscompile -gen client-config.xml


Next, we'll create a small client that uses these classes to test the service:

 import learningjava.impl.*;
  
 public class EchoClient
 {
 public static void main( String [] args ) throws java.rmi.RemoteException
 {
 Echo_Impl impl = new Echo_Impl( );
 EchoService service = impl.getEchoServicePort( );
  
 int i = service.echoInt( 42 );
 System.out.println( i == 42 );
 String s = service.echoString( "Hello!" );
 System.out.println( s.equals("Hello!") );
 MyObject myObj = service.echoMyObject( new MyObject(42, "Foo!") );
 System.out.println( myObj.getStringValue( ) );
 }
 }


To run this on the command line, use the appropriate client-env script to set your environment. (For Windows, you'll run the .bat file. For a Unix shell, you'll need to "source" the environment into your current shell using the source or . [dot] command.) Don't forget to set your JWSDP_HOME environment variable first. Then compile and run the EchoClient. You should see your values echoed back to you, including the value embedded in your value object. Of course, if you don't want to go through all of those steps, you can use the supplied Ant build to generate and run EchoClient for you using the generate-client and run-client targets. If you just want to see the code, use generate-client. The run-client target will invoke the generate-client for you (a smarter build would not rebuild if it wasn't necessary). Remember to edit the build.xml file to specify the location of the jwsdphome property.

 % ant run-client


And there we are. As we said in the introduction, the actual code required to implement and invoke our service is trivial. It's just the tools that are a little cumbersome, but they are improving every day. Web services are indeed a fast-paced and exciting area for Java. As with most growing things, there are sure to be awkward stages and unfortunate missteps. The power of the example we've shown here lies not in its technical capabilities (other protocols offer more features), but in its open portability and the simplicity of the basic XML-RPC model. A developer using another language could implement the same service or client using only simple XML tools. Some will certainly complain in the beginning that XML is too slow or too fluffy and that more optimal, binary formats would better serve us. All we have to do is look back at the growth of the Web and HTTP for examples of simplicity and transparency reigning over optimization. We'll talk about some of these aspects in , when we cover XML in depth. The other side of the coin is that many people will jump on the web services bandwagon for no technical reason at all, where other protocols, such as Java RMI or even plain HTTP, would do just fine. As with all things, it's important to evaluate the real-world needs and to invest time to understand the technology to make a wise decision.

Comments