Previous section   Next section

2.1 JAX-RPC Overview

JAX-RPC was designed to provide a simple way for developers to create web services and web service clients using techniques that are not very different from those used in nondistributed Java programming. Programming with JAX-RPC is very similar to using RMI to create a distributed application, in the sense that client code appears to be making ordinary method calls on local objects. In reality, however, the infrastructure handles these calls by converting them to messages that are sent over a network to the server, where they cause a local call to be made on the actual method implementation. The results of this call are used to create a reply message that is sent back the client, where they are extracted and presented as return values from the client application's method call.

Although there are similarities between RMI and JAX-RPC, the major difference arises from the fact that the messages exchanged between JAX-RPC clients and services are encoded using an XML-based protocol and can potentially be carried by a range of transport-level protocols, including HTTP (or its more secure variant HTTPS), SMTP, or even FTP. JAX-RPC allows a client written in the Java programming language to access a service implemented on, for example, the Microsoft .NET platform, whereas RMI clients and servers must both be written in Java (although it is possible to expose an RMI/CORBA hybrid service written in any language that has a binding to CORBA IIOP). In other words, it can communicate with foreign services without needing to be aware of the technology that its peer is actually using.

One of the benefits of using JAX-RPC over a lower-level web services technology such as SAAJ or JAXM (both of which will be covered later in this book) is that it doesn't require you to know much about XML before you can start building a distributed application. This is because, with a few exceptions that fall into the advanced category, the programming interfaces are completely independent of both the underlying messaging infrastructure and the transport protocol that is used to carry the XML messages. The JAX-RPC specification requires every implementation to support at least the use of SOAP over HTTP 1.1, but, as a developer, you can use JAX-RPC without having to be an expert in XML, SOAP, or HTTP. On the other hand, if these technologies are more than acronyms to you, as you'll see in Chapter 6, it is possible to use some of the more advanced JAX-RPC features to gain access to the lower levels. Here, you can directly handle SOAP headers or extend the set of data types that the client and server can exchange beyond those supported transparently by JAX-RPC.

2.1.1 The JAX-RPC Programming Model

This section introduces the JAX-RPC API by examining its programming model. At first, some of the concepts described here may seem a little abstract, especially if you are not familiar with another distributed programming technology, such as CORBA or RMI. In order to make things a little clearer, the following section will illustrate these concepts by relating them to a simple example.

2.1.1.1 Services, ports, and bindings

JAX-RPC uses web services terminology to describe some of its concepts. The meaning of some of these terms is represented in a diagram in Figure 2-1. A web service consists of a service endpoint interface (which is often referred to simply as an endpoint) that defines one or more operations that the web service offers. In order to promote portability and independence from the underlying communications mechanisms, web services are thought of as entities in their own right, separate from the protocol stacks used to gain access to them. Access to an endpoint is provided by binding it to a protocol stack through a port, which has an address that a client can use to communicate with it and invoke its operations.[1]

[1] The terms introduced in this section are based on those used by the Web Service Description Language (WSDL), which is covered in detail in Chapter 5.

Since the JAX-RPC specification requires that all implementations support the use of SOAP 1.1 messaging over HTTP 1.1 as the underlying transport protocol, the most common binding uses SOAP and HTTP, as shown in Figure 2-1. Bindings to other messaging systems and protocols are neither required nor precluded by the specification. In Figure 2-1, for example, port 1 provides access to the web service through a SOAP 1.1/HTTP protocol stack, port 2 uses SOAP 1.1 over HTTPS,[2] and port 3 exposes the same endpoint via a different (unspecified) protocol and messaging system.

[2] Support for HTTPS is not required by the JAX-RPC specification, but very often is required by real-world operation; it is included in the JAX-RPC reference implementation.

Figure 2-1. Web service terminology: endpoint, port, and binding
figs/jwsn_0201.gif

The nature of a port address depends partly on the protocol to which the endpoint is bound and partly on the JAX-RPC implementation. We'll see how the JAX-RPC reference implementation handles port addressing later in this chapter. In the case of an HTTP or HTTPS binding, port addresses are based on URLs. It is important to note that when you implement a web service using JAX-RPC, (provided that you don't use some of the more advanced features described in Chapter 6) your code will be independent of both the port address and the binding used by the client to access it, and therefore will not contain any SOAP- or HTTP-specific details.

2.1.1.2 JAX-RPC web service clients and servers

In terms of Java programming, JAX-RPC maps a web service operation to a Java method call and maps a service endpoint to a Java interface. One way to begin the implementation of a web service with JAX-RPC, therefore, is to create a Java interface that contains a method for each operation that the service will provide, along with a class that implements that interface. There are certain rules that need to be followed when defining both the interface and the methods that it contains. As we'll see in Chapter 6, JAX-RPC also allows you to import the definition of an existing web service in the form of a WSDL document and then generate from it the corresponding Java interface definition, in order to create either your own implementation of the service itself or a client that will use the service.

In a nondistributed programming environment, method calls are handled entirely by the Java virtual machine. For example, suppose you were to create a simple class like the one shown in Example 2-1, and another one that uses it, as shown in Example 2-2.

Example 2-1. A simple "service"
import java.util.Date;

public class DateService {
    public Date getDate(  ) {
        return new Date(  );
    }
}
Example 2-2. A simple "client"
import java.util.Date;

public class Test {
    public static void main(String[] args) {
        DateService  instance = new DateService(  );
        Date date = instance.getDate(  );
        System.out.println("The date is " + date);
    }
}

If you were to run the main( ) method of the class shown in Example 2-2, the getDate( ) method would be invoked directly within the same Java virtual machine as the main( ) method. In a distributed environment, however, the service implementation � that is, the DateService class and its getDate( ) method � would reside in a different Java virtual machine (and usually a different physical host) than the service client, which is the main( ) method in the Test class in this case. In these circumstances, the getDate( ) method call could not be dispatched directly by the client's virtual machine. Instead, a layer of software must be used to convey the method call from the client program to the server, carrying with it any arguments provided by the method caller (although in this case there are none) and returning the method call result (the Date object) to the client. This layer of software is provided by the JAX-RPC runtime system, as shown in Figure 2-2, in which the client application represents the Test class and the service implementation is the DateService class.

Figure 2-2. Clients, services, and the JAX-RPC runtime system
figs/jwsn_0202.gif

Although Figure 2-2 implies that the JAX-RPC runtime system is present on both the client and server systems, this will not always be the case. JAX-RPC supports interoperation with other XML-based RPC implementations, provided that they implement the SOAP 1.1 messaging protocol and use the same transport layer binding. The following list describes the supported software combinations.

Figure 2-3. JAX-RPC client interoperability with services implemented using a third-party product
figs/jwsn_0203.gif
Figure 2-4. JAX-RPC service interoperability with a client implemented using a third-party product
figs/jwsn_0204.gif

At the time of this writing, there are several SOAP 1.1 development environments available in addition to JAX-RPC, including Apache SOAP, Apache Axis, and GLUE, which are all Java-based, and SOAP::Lite for Perl developers. Perhaps most significantly, however, the Microsoft Visual Studio .NET development environment makes the creation of SOAP-based web services and web service clients for the Microsoft .NET platform relatively simple. As a result, web services created with JAX-RPC are easily accessible to clients written in Visual Basic or C# and running on .NET and vice versa.

2.1.1.3 JAX-RPC service creation

As a developer, you may find yourself in one of several possible roles when working with web services:

  1. Creating a web service together with the corresponding client or clients for in-house use

  2. Creating a web service to be made available locally or on the Internet

  3. Creating a client for an existing web service implemented by somebody else, possibly in a different organization

In the first case, where you will develop both the service and the client software that will be used to access it, it may be possible to use JAX-RPC on both the client and server systems. In the second case, the service itself may be implemented using JAX-RPC, but the clients, developed by other groups within your company or by users in other companies, could be built on the .NET platform or using a different Java SOAP implementation. Finally, in the third case, a client for an existing web service can be written using JAX-RPC, provided that the service is RPC-based and uses only data types that JAX-RPC can support or for which you can write extensions.[3]

[3] In order to support new data types, you need to write a custom serializer that knows how to convert between the data type and a corresponding XML representation. At the time of this writing, the JAX-RPC specification does not provide a framework for writing serializers that are portable between JAX-RPC implementations, and the API used by the reference implementation to create them is, therefore, not part of the specification. Consequently, you should consider creating custom serializers only if there is no other choice.

In all three cases, there needs to be a definition of the web service that describes the operations that it provides and the data types that they require as arguments and provide as return values. In addition, but of less interest in this chapter, it is necessary to define how the information that moves between the client and the server when operations are performed is mapped onto the protocols to which the JAX-RPC (or third party) implementation is bound�that is, how the SOAP 1.1 messages that are exchanged are to be constructed. Service definitions that contain all of this information are typically made available as a document written using the Web Service Description Language (WSDL), which is covered in Chapter 5.

From the client developer's point of view, having a WSDL description of someone else's service to work with is useful even if you don't know very much about WSDL, because JAX-RPC can read such a document and generate from it the Java code required to link your client code to the service, leaving you to write only the business logic of the client application itself. Since WSDL is a standardized language based on XML, JAX-RPC can do this for any web service, whether it was originally implemented using JAX-RPC, on the .NET platform, or in any other way. If you'd like to know what a WSDL document looks like, flip forward to the Appendix at the back of this book, which contains a couple of representative examples.

For the benefit of the server developer, having defined your service as a Java interface, you can avoid the tedious task of manually creating the corresponding WSDL document by using a tool provided by JAX-RPC. Once the WSDL is created, it is typically advertised at a well-known URL or in a registry so that client implementors can find and import it. WSDL file publication and discovery can be handled using facilities provided by another J2EE technology called the Java API for XML Registries (JAXR), which is described in Chapter 7.

2.1.1.4 JAX-RPC client and server programming environments

JAX-RPC supports the creation of clients that are implemented either as freestanding J2SE applications, as J2EE client applications (that is, applications that operate within the J2EE client container), or within a web or EJB container. A freestanding JAX-RPC client application is typically a rich GUI client implemented with Swing or AWT, while a container-based client might be embedded in a servlet or an Enterprise JavaBean (EJB) that is part of a J2EE-hosted web application. In the future, support will be provided for freestanding JAX-RPC clients on small devices that host the Java 2 Micro Edition (J2ME) platform.

On the server side, the JAX-RPC specification envisages that a JAX-RPC service will be implemented as either a servlet or an EJB, although the specification itself covers only the programming model for a service hosted by a servlet. In support of this, the JAX-RPC reference implementation provides a servlet that can be used to direct SOAP messages received over an HTTP transport to the actual web service implementation. The details of this mechanism will be covered later in this chapter. The implementation of a JAX-RPC service within an EJB is outside of the scope of the JAX-RPC specification itself. However, support for EJB-hosted web services is an integral part of the J2EE 1.4 platform and is discussed later in this chapter. For the most part, however, you don't need to care too much about which environment your service is running in, since most of the details are the same in both cases.

The classes that form the JAX-RPC API�many of which are available to both client- and server-side code�are distributed over the small set of packages listed in Table 2-1.

Table 2-1. Packages in the JAX-RPC API

Package name

Description

javax.xml.rpc

Core classes that provide the client-side programming model.

javax.xml.rpc.encoding

Classes that perform the conversion of Java primitives and other supported data types to and from the XML representation used in SOAP messages.

javax.xml.rpc.handler

javax.xml.rpc.handler.soap

Classes that process the XML messages sent and received during request and response handling. Developers can create custom handlers that may be invoked during message processing on both the client and server sides to perform specialized tasks such as data encryption or other security services. Creation of message handlers requires an understanding of SOAP. This is covered in Chapter 6.

javax.xml.rpc.holders

Classes that support the use of output or input-output parameters in JAX-RPC method calls. Since Java does not directly support the notion of parameters whose value can be changed as the result of a method call (i.e., parameters that have call-by-reference semantics), these classes can be used to wrap the actual parameters in order to provide that capability.

javax.xml.rpc.holders

Classes that support the use of output or input-output parameters in JAX-RPC method calls. Since Java does not directly support the notion of parameters whose value can be changed as the result of a method call (i.e., parameters that have call-by-reference semantics), these classes can be used to wrap the actual parameters in order to provide that capability.

javax.xml.rpc.server

This package contains the minimal API (only two interfaces) provided by JAX-RPC for the use of web service implementation classes.

javax.xml.rpc.soap

This package contains the JAX-RPC classes that are specific to the SOAP binding of the JAX-RPC API. At the time of this writing, it consists of a single exception class (SOAPFaultException).

2.1.1.5 Stubs and ties

When a JAX-RPC client invokes an operation provided by a web service, the method that it calls is not the one that will actually perform the operation, since, as shown in Figure 2-2, the service implementation does not reside in the same Java virtual machine as the client. Instead, the client-side JAX-RPC runtime converts the call into a message that is sent to the server-side JAX-RPC runtime to be dispatched to the actual service class method implementation. For simplicity, however, it is desirable for the client code to look as similar as possible to that shown in Example 2-2, even though it cannot directly invoke a method on the actual implementation class.

To make this possible, JAX-RPC provides a stub object that has the same methods as the service implementation class.[4] The client application is linked with the stub and invokes a stub method, which is then delegated to the client-side JAX-RPC runtime so that the appropriate SOAP message can be sent to the server. When the method call is completed on the server, the result is sent back to the client-side JAX-RPC runtime and then forwarded to the client stub, which returns it as the result of the client application's method call. Figure 2-2 shows where stubs reside in the client-side JAX-RPC implementation.

[4] If you are familiar with CORBA or RMI programming, you'll recognize that the same stub and tie programming model is being used here.

Similarly, on the server side, the message received as a result of a client's method call must be converted into a method call on the actual service implementation. This functionality is provided by another piece of glue software, called a tie, that knows how to extract the method name and parameters from an incoming SOAP 1.1 message and use them to invoke the required service method. The tie also converts the result of the method call back into a response message to be returned to the client JAX-RPC runtime system.

Since the stub and tie classes have to be able to handle the same methods as the service endpoint that the client wishes to use, they depend on the definition of the service endpoint created by the web service developer. They also need to be coded to use the underlying JAX-RPC runtime to create, send, and receive SOAP 1.1 messages. Fortunately, the developer does not have to write these classes � instead, JAX-RPC implementations are required to provide tools that generate them. The details of this process are not part of the JAX-RPC specification; therefore, vendors are free to implement this functionality in any way that they see fit. As you'll see shortly, the JAX-RPC reference implementation includes command-line programs that can be used to create stubs and ties from either a WSDL document or a Java interface definition. Other vendors might include this functionality as part of an integrated development environment (IDE), and generate the stub and tie classes automatically as part of the normal process of running an application or deploying a service from within the IDE.

2.1.1.6 Client invocation modes

JAX-RPC provides two different modes of operation that client applications may use when invoking service methods. The first and most familiar mode is synchronous request-response, illustrated in Figure 2-5. This mode works exactly like an ordinary Java method call, in that once the client has invoked the method, it blocks until the service performs the requested operation and either returns the results or throws an exception. In terms of message exchanges, once the client JAX-RPC runtime has generated and sent the request message, it waits for its counterpart on the server-side to return a response message and then delivers the results to the client application.

Figure 2-5. Synchronous method invocation
figs/jwsn_0205.gif

JAX-RPC also supports a less coupled mode of operation referred to as a one-way RPC. In this mode, shown in Figure 2-6, the client does not expect a reply from the service and therefore does not block after the request has been sent. Note that a one-way RPC is not represented by the following style of Java method call:

public void request(int arg1, int arg2);

Even though a method defined in this way does not return a value to the caller, if it appeared in a web service interface definition, it would actually be mapped to a synchronous request-response RPC and not a one-way RPC. A response from the server is required even though the method return type is void because, even though it would not contain a return value, it might still need to indicate that an exception should be thrown to the client. A one-way RPC, by contrast, cannot result in an exception being thrown from the service implementation and therefore cannot report an error condition to the initiating client.

Figure 2-6. One-way RPC invocation
figs/jwsn_0206.gif

There is no Java method call syntax that correctly reflects the semantics of a one-way RPC call. For this reason, client stubs support only the synchronous request-response mode. In order to make a one-way RPC call, an application has to bypass the stubs and make a Dynamic Invocation Interface (DII) call. DII calls are discussed in Chapter 6.

2.1.2 A Simple JAX-RPC Example

Now that you've seen the basic concepts of JAX-RPC, it's time to see how it works in practice by looking at a simple example that shows how to define a web service endpoint, deploy the server implementation, and, finally, invoke it from a client application. In this section, we're not going to look in much detail at the JAX-RPC API itself or the code that implements either the client or the service � instead, the focus is on demonstrating how to use the JAX-RPC reference implementation to quickly create a web service from scratch. The details will be covered later in the chapter.

2.1.2.1 Defining the service interface

Since WSDL is the universally understood language for describing web services, the JAX-RPC specification requires all implementations to provide a mechanism to convert a WSDL document into the corresponding Java interface definition, together with the stubs and ties that allow you to implement the service itself and client applications that will invoke it. From a developer's viewpoint, though, having to create a WSDL definition of a new web service as the first step is far from convenient. Fortunately, although it is not mandatory, the specification also allows implementations to support other ways of specifying a web service. As already mentioned, the JAX-RPC reference implementation accepts service definitions in the form of class files that contain compiled Java interfaces. From these class files, it creates not only the stubs and ties, but also a WSDL file that represents the web service. This is convenient because it means that you don't need to learn to write WSDL documents before you can get started writing web services.

We'll use this approach to demonstrate the steps necessary to create a simple web service that provides information about books. Given the title of a book, the service can return specific attributes such as its price or the name of its author. It also provides a way to get a listing of all available books, together with full information for each of them. A Java interface that represents the service endpoint interface definition for the book web service is shown in Example 2-3.

Example 2-3. The BookQuery interface
package ora.jwsnut.chapter2.bookservice;

import java.util.HashMap;
import java.rmi.Remote;
import java.rmi.RemoteException;

/**
 * The interface definition for the book
 * web service.
 */
public interfaceBookQuery extends Remote {
    // Gets the number of books known to the service
    public abstract int getBookCount(  ) throws RemoteException;
    
    // Gets the author of a book given its title
    public abstract String getAuthor(String name) throws RemoteException;

    // Gets the editor of a book given its title
    public abstract String getEditor(String name) throws RemoteException;
    
    // Gets the price of a book given its title
    public abstract double getPrice(String name) throws BookServiceException,
                                                        RemoteException;
    
    // Gets information for  all books known to the service in 
    // the form of an array
    public abstract BookInfo[] getBookInfo(  ) throws RemoteException;

    // Gets information for all books in the form of a HashMap in which the key 
    // is the book's title in upper case and the value is a BookInfo object
    public abstract HashMap getBookMap(  ) throws RemoteException;
}

The first point to notice about this definition is that the interface extends java.rmi.Remote. This is a requirement of all JAX-RPC service endpoint interface definitions. The second important point is that every method in the interface is defined to throw a java.rmi.RemoteException. This is also a mandatory requirement and it allows the JAX-RPC runtime to use this exception to report communication problems that might occur during the exchange of SOAP messages required to complete a method call.

Aside from these requirements, the methods are declared in the same way as they would be if they were intended to be implemented by a class in the local Java virtual machine. As you'll see later, there are some restrictions on the types of arguments that can be used and on the values that can be returned. In this case, most of the methods use only Java primitive types and Strings. The getBookInfo( ) and getBookMap( ) methods, however, are slightly different.

The getBookMap( ) method returns all of the books that the service knows about in the form of a HashMap. Here, we are using an extension that is part of the JAX-RPC reference implementation, because support for Java collections as method arguments and return values is not required by the JAX-RPC specification.

The getBookInfo( ) method is defined to return an array of objects of type BookInfo, a simple class that is part of the web service interface definition. JAX-RPC supports the use of certain types of developer-defined Java objects, both as method parameters and return values, provided they meet certain simple criteria that are discussed later. If you need to use an object of a type that does not meet these criteria, you need to write a custom serializer, which is a nontrivial task that is not supported in a portable manner at the present time.

The definition of the BookInfo class is shown in Example 2-4.

Example 2-4. The BookInfo class
package ora.jwsnut.chapter2.bookservice;

/** 
 * A class that holds information relating
 * to a book known to the book web service.
 */
public class BookInfo {
    
    private String title;
    private String author;
    private String editor;
    private double price;
    
    // Constructs an uninitialized BookInfo object.
    public BookInfo(  ) {
    }
    
    // Constructs a BookInfo object initialized with given attributes.
    public BookInfo(String title, String author, String editor, double price) {
        this.title = title;
        this.author = author;
        this.editor = editor;
        this.price = price;
    }
    
    // Gets the title of the book
    public String getTitle(  ) {
        return title;
    }
    
    // Gets the author of the book
    public String getAuthor(  ) {
        return author;
    }
    
    // Gets the name of the editor the book
    public String getEditor(  ) {
        return editor;
    }
    
    // Gets the price of the book in USD
    public double getPrice(  ) {
        return price;
    } 
        
    // Sets the title of the book
    public void setTitle(String title) {
        this.title = title;
    }
    
    // Sets the author of the book
    public void setAuthor(String author) {
        this.author = author;
    }
    
    // Sets the name of the editor the book
    public void setEditor(String editor) {
        this.editor = editor;
    }
    
    // Sets the price of the book in USD
    public void setPrice(double price) {
        this.price = price;
    }    
}

This class is simply a holder for information returned from the web service to its clients. Aside from providing methods to set and retrieve its attributes, it has no useful behavior. To use a term that is familiar to J2EE developers, this is an example of a value type. Value types are one of the types of method arguments and return values that JAX-RPC supports.

Since the client application is not expected to modify the content of a BookInfo object, it seems inappropriate to incorporate public methods such as setAuthor( ) that seem to encourage this behavior. Unfortunately, for reasons that we'll cover later, JAX-RPC requires you to declare these public mutator methods, or make the attributes themselves public, which is an even worse alternative.

Notice that, in addition to RemoteException, the getPrice( ) method of the BookQuery interface shown in Example 2-3 is declared to throw a BookServiceException. JAX-RPC allows methods to throw service-specific exceptions that are derived from java.lang.Exception, but not errors (i.e., subclasses of java.lang.Error). In this case, BookServiceException is derived directly from java.lang.Exception, as shown in Example 2-5.

Example 2-5. A JAX-RPC service-specific exception
package ora.jwsnut.chapter2.bookservice;

/**
 * A service-specific exception that reports
 * problems while executing methods of the book
 * web service
 */
public class BookServiceException extends Exception {
    
    // Constructs a BookServiceException with an associated message
    public BookServiceException(String message) {
        super(message);
    }

    // Gets the message associated with this exception
    public String getMessage(  ) {
        return super.getMessage(  );
    }
}

Notice that this exception class defines a getMessage( ) method that returns the message set in the constructor, even though a method with the same name and signature is inherited from its superclass. If this were not done, a BookServiceException thrown by the service implementation would not be translated to a BookServiceException on the client side. The exact conditions that must be met when defining an exception class in order for the exception to be properly reported to a client application are:

If any of these conditions is not met, then a SOAPFaultException will be thrown in the client instead of the application-specific exception thrown by the service.

You have now seen all of the source code that makes up the definition of the book web service. There are a couple of points to note about this code:

2.1.2.2 Implementing the service

Having defined the service endpoint interfaces, the next step is to write the code for the service itself. The service implementation can be spread over as many classes as you like, provided that there is at least one class, usually referred to as a servant, that meets the following conditions:

The JAX-RPC specification requires not only that the servant provide implementations of the methods defined in the service interface, but also that it declares, using the Java implements keyword, that it implements that interface. However, the J2EE Web Services specification (JSR 109) relaxes this requirement, making it optional for the servant class to make this declaration, subject to the proviso that if it chooses not to, then the methods themselves must not be final. At the time of this writing, the JWSDP imposes the requirement of the JAX-RPC specification and therefore, for the sake of compatibilty, the servant classes for all of the examples in this book declare that they implement the service endpoint interface. J2EE 1.4 follows JSR 109 and does not insist on this declaration.

Provided you follow these rules, there is nothing special about writing a JAX-RPC servant class. In fact, apart from a couple of interfaces defined in the javax.xml.rpc.server package that allow a servlet-based servant to interface to the servlet container within which it is running, there is no server-side JAX-RPC API. These interfaces will be covered in Chapter 6.

The JAX-RPC specification does not specify how the methods of the servant class are invoked in response to a method call made by an application client. For services that are hosted in a web container, the reference implementation provides a servlet that uses the tie classes to extract the method arguments from a call message, invokes the appropriate servant method, and then builds the reply message. A similar arrangement is provided for EJB-hosted web services. Since these details are all handled by the JAX-RPC runtime, you don't need to concern yourself with them when creating the service implementation.

The implementation of the book web service consists of two Java classes and a text file that contains the list of books that the service knows about, together with the information to be provided in the BookInfo objects for each book. Since much of the code is concerned with reading the text file and building the book list, I'm not going to include all of it here. If you download the example source code and install it as described in the section Examples Online in the Preface, you'll find all of the code for these classes in the folder chapter2\bookservice\server\ora\jwsnut\chapter2\bookservice. The code for the servant class, BookServiceServant, which actually implements the service endpoint interface, is shown in Example 2-6.

Example 2-6. Implementation of the book web service
package ora.jwsnut.chapter2.bookservice;
import java.util.HashMap;

/**
 * Implementation class for the book web service
 */
public class BookServiceServant implements BookQuery {
    
    public int getBookCount(  ) {
        return BookServiceServantData.getBookInfo(  ).length;
    }
    
    public String getAuthor(String name) {
        BookInfo book = findBook(name);
        return book == null ? null : book.getAuthor(  );
    }
    
    public String getEditor(String name) {
        BookInfo book = findBook(name);
        return book == null ? null : book.getEditor(  );
    }
    
    public double getPrice(String name) throws BookServiceException {
        BookInfo book = findBook(name);
        if (book == null) {
            // No such book - throw an exception
            throw new BookServiceException("No matching book for '" + 
                 name + "'");
        }
        return book.getPrice(  );
    }
    
    public BookInfo[] getBookInfo(  ) {
        return BookServiceServantData.getBookInfo(  );
    }
    
     public HashMap getBookMap(  ) {
        return BookServiceServantData.getBookInfoHashMap(  );
    }
    
    /* -- Implementation details -- */
    private BookInfo findBook(String name) {
        BookInfo[] books = BookServiceServantData.getBookInfo(  );
        for (int i = 0; i < books.length; i++) {
            if (books[i].getTitle(  ).equalsIgnoreCase(name)) {
                // Found a match
                return books[i];
            }
        }
        return null;        // No match
    }
}

The important things to note about this class are:

The data for this service is managed in a separate helper class called BookServiceServantData, the implementation of which is uninteresting apart from the code shown in the following extract:

               InputStream is =  BookServiceServantData.class
                 .getResourceAsStream("booklist.txt");
                BufferedReader reader = new BufferedReader(
                  new InputStreamReader(is));
                String line;
                while ((line = reader.readLine(  )) != null) {
                    StringTokenizer st = new StringTokenizer(line, "!");
                    if (st.countTokens(  ) == 4) {
                        list.add(new BookInfo(st.nextToken(  ), 
                                st.nextToken(  ),
                                st.nextToken(  ), 
                                Double.parseDouble(st.nextToken(  ))));
                    }
                }

The significant point here is that the book data is kept in a file called booklist.txt that resides in the same package as the BookServiceServantData class itself. Because there is nothing special about a JAX-RPC service implementation, this file can be treated as a resource and therefore can be located at runtime in the usual way, using the getResourceAsStream( ) method of java.lang.Class.

Since JAX-RPC services will ultimately be deployed in a web or EJB container of a J2EE-based application server, another way to obtain initialization data is from the container's JNDI environment or, for a servlet-based implementation, from the initialization parameters of the servlet that invokes the service's methods. You'll see how to get access to the servlet's initialization parameters as part of the discussion of the ServletLifecycle interface in Chapter 6.

2.1.2.3 Writing the client code

For the purposes of demonstrating the book web service, the example code contains a freestanding J2SE client application that invokes the methods of the BookQuery interface and displays the results that it receives. The source for this application can be found in the file chapter2\bookservice\client\ora\jwsnut\bookservice\client\BookServiceClient.java relative to the installation directory of the example source code for this book.

To invoke the methods of a service endpoint interface, a client application needs to get a reference to an object that implements that interface. Obviously, it cannot simply instantiate the BookServiceServant class in order to do this � instead, as you can see from Figure 2-2, it has to get a reference to a generated stub object that implements the service endpoint interface. Unfortunately, the JAX-RPC specification does not fully specify the naming convention to be used for stub classes. Instead, it makes the following statement:

The name of a generated stub class is either <BindingName>_Stub or is implementation specific.

As a result of this rather loose requirement, it is not possible to write portable code that refers directly to generated stub classes. If you need to write fully portable code, then you have two choices:

If you are using the JAX-RPC reference implementation, you can obtain and use a stub using the following code:

// Get a reference to the stub and set the service address
BookService_Impl  service = new BookService_Impl(  );
BookQuery  bookQuery = (BookQuery)service.getBookQueryPort(  );
((Stub)bookQuery)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, args[0]);

BookInfo[] books = bookQuery.getBookInfo(  );

As you'll see in Section 2.2 later in this chapter, BookService_Impl is one of the classes that is generated along with the stubs themselves. The BookService_Impl class contains a generated method that allows an instance of the stub for the service endpoint interface to be obtained. The stub object implements both the BookQuery service endpoint interface and the interface javax.xml.rpc.Stub, which is part of the JAX-RPC client-side API. This latter interface provides a method (called _setProperty( )) that allows the information that the stub needs in order to communicate with a server that contains the actual service implementation to be supplied. The only information that the stub requires is the address to which the call message should be sent, which must be set using the stub's ENDPOINT_ADDRESS_PROPERTY. We'll look in more detail at the properties of stubs and the exact format of the address in Section 2.2.2, later in this chapter.

Once the address is set, the stub can be used to make any number of method calls, which look exactly like local calls:

BookInfo[] books = bookQuery.getBookInfo(  );

Looking at this code, it is natural to think of the object referenced by bookQuery as being a single object at the server on which method calls can be made. However, this is not necessarily the case. If you make two separate method calls using the same bookQuery reference, those calls might actually be dispatched to two different server-side objects, depending on the way in which the JAX-RPC server-side environment is implemented. In this example, that isn't important, since the service endpoint interfaces defined here simply query the state of a static set of books. However, it is important if we attempt to define an interface that requires several method calls to set up conditions within the target object followed by a call that performs some operation based on those conditions, since not all of the conditions will necessarily be set on the same server-side object instance. In other words, the server-side object that implements the service cannot be assumed to maintain state relating to any of its clients.[5]

[5] The server-side JAX-RPC API includes a provision for a servlet-hosted web service implementation to preserve per-client state across web service method calls, provided that the client allows it. See Section 6.7.5 for an example that demonstrates this feature.

2.1.2.4 Example source code organization

When you create a web service, you need to write the source code for the interface definition and the service implementation classes and, at least for testing purposes, you will most likely also create a client application. As well as your own source code, the build process creates class files and (optionally) source code for the stub and tie classes. For the sake of clarity, the example source code for this chapter is organized into a directory structure, shown in Figure 2-7, that reflects the function of each class and its relationship to the other components of the overall system. It is clearly desirable to separate the client application from the service implementation, for example, because in the real world the client and the service may be developed independently and possibly by different developers working in different companies. Similarly, the source code that defines the service endpoint interface should be maintained separately from the service implementation.

Figure 2-7. Example source code organization
figs/jwsn_0207.gif

The top-level directory, chapter2, contains a subdirectory called bookservice that contains all of the source code for this example. The Java source file for the service endpoint interface definition is placed beneath the interface subdirectory, in a directory hierarchy organized according to the usual package naming conventions and, similarly, the service implementation is held below the server subdirectory. For this example, both the interface definition and the service implementation code are in the same package (ora.jwsnut.chapter2.bookservice), although this is not required by JAX-RPC � in fact, JAX-RPC does not place any restrictions on the mapping of classes to packages when you create your own service endpoint interface.[6] The client code is held separately in the client subdirectory and also resides in a separate package (ora.jwsnut.chapter2.client). Again, this is done for the sake of clarity.

[6] If you start with a WSDL file instead of Java service definitions, all of the generated classes are placed in the same package by default. However, it is possible to force some of the generated classes to be placed in different packages based on the XML namespace within which they are defined. For more details, refer to Chapter 6.

2.1.2.5 Compiling and running the book web service example

Once the code is written, deployment and testing of a web service requires that the following steps are performed:

  1. The service endpoint interface and the service implementation classes must be compiled.

  2. The server-side code must be bundled into a Web Archive (WAR) file, together with the appropriate tie classes.

  3. The WAR must be deployed into the target web container or J2EE-based application server.

  4. Client-side stubs must be generated.

  5. The client code must be compiled.

To simplify this process, the examples in this book are all built and run using the Ant build tool, which is included in both the J2EE 1.4 reference implementation and the Java Web Services Developer Pack.[7] Before you can run the examples, you need to set up environment variables used by the JAX-RPC reference implementation as well as create a file called jwsnutExamples.properties in your home directory that contains information specific to your system that will be used by the Ant buildfile to locate source files and deploy the web service. If you have not already done so, refer to Examples Online in the Preface for a description of what is required.

[7] You don't need to know anything about Ant to be able to run the example source code in this book. However, Ant is an extremely useful tool for all kinds of Java development. If you're not familiar with it (or even if you are), I recommend getting a copy of Ant: The Definitive Guide, by Jesse Tilly and Eric M. Burke (O'Reilly).

To use the example source code, open a command window and make the bookservice directory your working directory. Here, you will find three files that are relevant to the build process, as shown in Figure 2-8:

build.xml

This is the build control file for Ant. This XML file contains a set of targets that you can use to build the interface definitions, the service implementation, and the client, as well as use to run the example.

config.xml

This file tells the command-line utility wscompile (which will generate client-side stubs that will be used to access the web service) where to find the definition of the service endpoint interface for the book web service. We'll look at this file in detail in Section 2.2.2 later in this chapter.

example.properties

Because many of the tasks involved in building and deploying web services are common, most of the content of the build.xml file is actually stored in a separate location from which it is imported when it is read by Ant. The example.properties file contains settings that tailor the common build process for this particular example.

Figure 2-8. Build-related files for the book web service
figs/jwsn_0208.gif

There are also some files that are related to the server-side deployment process, which you'll find in the deploy and deploy-j2ee14 subdirectories. There are two sets of files because the deployment process required for J2EE 1.4 is different from that used with the JWSDP. These files are discussed in Section 2.2.4, later in this chapter.

The build.xml file contains a number of targets that can be used to perform selected parts of the build process, to deploy the web service, or to run the client application. To call one of these targets, use a command such as:

ant target

where target is one of those listed in Table 2-2.

Table 2-2. Targets in the Ant buildfile for the book web service

Target

Description

compile-interface

Compiles the service endpoint interface definitions from the interface subdirectory.

compile-server

Compiles the classes in the server subdirectory that make up the service implementation.

generate-client

Runs wscompile to generate the client stub classes.

compile-client

Compiles the classes in the client subdirectory that make up the application clients for this web service.

compile

Compiles both the service implementation and the application clients.

portable-web-package

Packages the service implementation as a WAR file.

web-package

Uses the file created by the portable-web-package target to create a deployable WAR file by generating and adding tie classes and the WSDL file for the service.

deploy

Compiles, packages, and deploys the book web service in the J2EE reference implementation application server or the Tomcat web container included with the Java Web Services Developer Pack.

undeploy

Removes the book web service from the web container.

redeploy

Replaces the current version of the book web service in the web container by undeploying it and deploying the most recently packaged version.

run-client

Runs one of the application clients.

run-client-bookmap

Runs the other application client.

clean

Deletes all generated and compiled files and output directories, leaving only the original source files.

In most cases, these targets invoke other targets as necessary to complete their tasks so that, for example, if you use the compile-server target, it will automatically invoke the compile-interface target that creates the inputs that it requires.

The easiest way to run the book web service example is to start by generating the files required for the server-side deployment using the following command:

ant web-package

This target causes Ant to compile the service interface definitions and the service implementation classes, and create a web archive containing everything necessary to deploy the service.[8] During this process, two extra directories are created beneath the bookservice directory:

[8] The details of this process will be described in more detail in Section 2.2.4, later in this chapter.

Compiling the interface definitions and the service implementation files is a simple matter of running the Java compiler in the usual way, specifying the appropriate location in which to store the class files beneath the output directory. The only point to note is that the CLASSPATH passed to the compiler needs to include the classes that make up the JAX-RPC API. For the J2EE 1.4 platform, these classes are bundled into the file lib\j2ee.jar, which contains the entire J2EE API, whereas for JWSDP 1.1, they can be found in the JAR file jaxrpc-api.jar in the jaxrpc-1.0.3\lib directory. The Ant buildfile takes care of setting the correct CLASSPATH, but you will need to ensure that you include the appropriate JAR files if you intend to compile JAX-RPC services or applications from the command line.

Having created the web archive, which will be written to the file chapter2\bookservice\Books.war, the next step is to make the service available by deploying it either in a web container or in a J2EE-compatible application server. There are several different ways to perform the deployment, but the simplest approach is to use the deploy target in the Ant buildfile:

ant deploy

Having deployed the service, if you want to make changes to it, some containers (including Tomcat) require you to undeploy the existing instance before you can install a new one. The Ant buildfile provides a target called undeploy that will do this for you, as well as a target called redeploy that combines the undeployment and deployment steps.

A quick way to check that the service is properly deployed is to open a browser and point it at the URL

http://hostname:port/Books/BookQuery?WSDL

where hostname and port correspond to the HTTP port of the J2EE application server or the Tomcat web container for the JWSDP. If you are using J2EE 1.4, the appropriate URL is:

http://localhost:8000/Books/BookQuery?WSDL

whereas if you have installed the JWSDP with the default setup, the web container uses port 8080 instead of port 8000; therefore, the URL is:

http://localhost:8080/Books/BookQuery?WSDL

If the service is properly deployed, then in the case of the JWSDP, this URL will return a page of XML tags like that shown in Figure 2-9, which is actually the WSDL definition for the deployed web service. WSDL is described in detail in Chapter 5 of this book.

Figure 2-9. WSDL definition for a deployed web service
figs/jwsn_0209.gif

The next step is to build the application client code, which can be done using the compile-client target of the Ant buildfile:

ant compile-client

There are actually two application clients, which you can run by using the run-client or run-client-bookmap targets. The client run by the run-client-bookmap target, for example, uses the getBookMap( ) method of the BookQuery interface to fetch the complete set of books known to the service in the form of a HashMap, in which the key to each entry is the book title in uppercase and the associated value is the BookInfo object for that book. Having retrieved the HashMap, it prints the key and value for each of its entries. Type the following command:

ant run-client-bookmap

You should see output that looks like this (only a subset is actually shown):

     [java] KEY: [JAVA IN A NUTSHELL], value = Java in a Nutshell by David 
Flanagan, edited by Paula Ferguson, Robert Eckstein, price USD 39.95
     [java] KEY: [J2ME IN A NUTSHELL], value = J2ME in a Nutshell by Kim 
Topley,  edited by Robert Eckstein, price USD 29.95
     [java] KEY: [JAVA I/O], value = Java I/O by Elliotte Rusty Harold, 
edited by Mike Loukides, price USD 32.95
     [java] KEY: [JAVA 2D GRAPHICS], value = Java 2D Graphics by Jonathan 
Knudsen, edited by Mike Loukides, price USD 29.95
      [java] KEY: [JAVA SWING], value = Java Swing by Robert Eckstein et al, 
edited by Mike Loukides, price USD 44.95
     [java] KEY: [JAVA SERVLET PROGRAMMING], value = Java Servlet 
Programming by Jason Hunter, William Crawford, edited by Paula Ferguson, 
price USD 32.95

The other client can be used to invoke any of the remaining methods of the BookQuery interface, depending on the arguments supplied on its command line. The client's command line looks like this:

java ora.jwsnut.chapter2.bookservice.BookServiceClient  url   command   
title

Here, url is the address of the BookQuery service endpoint interface, command indicates which interface operation is to be called, and title is the book title to be supplied as the argument to the operation. If the command and title arguments are omitted, the client uses the getBookInfo( ) method to get a book list and prints the result. You can try this out using the command:

ant run-client

This produces a result that is similar to that shown earlier, except that the key values are not present because the return value is an array of BookInfo objects instead of a HashMap.

The allowable values for the command argument are author, editor, and price. Selecting one of these values causes the application client to invoke the BookQuery interface's getAuthor( ), getEditor( ), or getPrice( ) method, passing the book title obtained from the remaining command-line arguments as its parameter. The BookInfo object that is returned is then printed. To supply command-line arguments to this client using Ant, set the CLIENT_ARGS property from the command line and execute the run-client target. To get the name of the editor of the book Java Swing, for example, use the following command:

ant -DCLIENT_ARGS="http://localhost:8000/Books/BookQuery  editor Java Swing" 
run-client

This command produces the following result:

run-client:
     [java] NAME = [Java Swing]
     [java] Mike Loukides

The URL used in this command:

http://localhost:8000/Books/BookQuery

indicates that the method to be called belongs to the BookQuery interface of a web service deployed in a web application called Books. The fact that such a simple and obvious address exists for this service endpoint interface is determined by configuration information supplied among the deployment files for this service, the details of which will be shown in Section 2.2.4, later in this chapter.[9]

[9] Almost all of the web service URLs that you'll see in this book use port number 8000, since this is the default HTTP port for the web container in the J2EE 1.4 reference implementation. If you are using the examples with the JWSDP, you should use port 8080 instead. In most cases, however, you won't need to concern yourself with this difference because the Ant buildfile targets that run the example applications handle this difference for you by obtaining the port number from the jwsnutExamples.properties file.

In order to run the application client, the Ant buildfile sets up the appropriate CLASSPATH so that all of the JAR files that the JAX-RPC reference implementation relies on are available. The CLASSPATH required to run a client application is larger than that required to compile it because it is necessary to include the classes for a specific JAX-RPC implementation, whereas the compilation step requires only the implementation-independent JAX-RPC API classes. If you are using the JWSDP, the complete set of files required is quite large:

jaxrpc-api.jar

jaxrpc-ri.jar

saaj-api.jar

saaj-ri.jar

activation.jar

commons-logging.jar

dom4j.jar

mail.jar

jaxp-api.jar

dom.jar

sax.jar

xalan.jar

xercesImpl.jar

xsltc.jar

   

These JAR files can be found in various subdirectories of the JWSDP installation. In the case of the J2EE 1.4 platform, the CLASSPATH needs only to include the lib\j2ee.jar file and a small number of additional JAR files that can be found in the lib/endorsed subdirectory of the reference implementation.


  Previous section   Next section