2.2 Programming with JAX-RPC

   

2.2 Programming with JAX-RPC

Now that you've seen a simple example of JAX-RPC programming, this section lifts the hood a little and looks more closely at some of the details that were skimmed over in the first part of this chapter. Although much of what follows is completely generic, it isn't possible to give a complete description of JAX-RPC programming without going beyond the bounds of the specification, since there are certain aspects of the programming model that vendors are permitted to implement in any convenient manner. Where this is the case, we'll make it clear that what is being described is not covered by the specification, and we'll use the JAX-RPC reference implementations in the J2EE 1.4 platform and the JWSDP as typical examples.

2.2.1 The JAX-RPC Service Interface

Web service development with JAX-RPC begins either with the definition of the service itself as a Java interface or by importing a service definition in the form of a WSDL document. In this chapter, for the sake of simplicity, we consider only the first case and defer discussion of WSDL and of using WSDL as the starting point for JAX-RPC development to Chapter 5 and Chapter 6, respectively. As noted earlier, using Java to define the service endpoint interface is not sufficient if you want to publish the service so that it can be used by clients written in other programming languages. In order to be truly open , the service definition has to be exported as a WSDL document. However, using Java as the definition language is convenient at this stage since we haven't yet described WSDL. Therefore, we will continue to discuss JAX-RPC service definitions and the rules that apply to them in terms of their bindings to the Java language for the rest of this chapter.

2.2.1.1 Interface method definitions

A Java web service endpoint interface must obey the following rules:

  • The interface must extend java.rmi.Remote .

  • Each interface method must declare that it throws java.rmi.RemoteException .

  • A method may additionally throw service-dependent exceptions, as long as they are checked exceptions derived from java.lang.Exception .

  • Method name -overloading is permitted, subject to the usual rules of the Java language.

  • Service endpoint interfaces may be derived by extension from other interfaces.

Arguments passed to the methods of an endpoint interface are passed to the service implementation by value. Where an argument is an object, its value is copied before being sent to the server. Return values that are objects are newly created during the processing of the reply message from the server. The copying and creation of objects in this way by the JAX-RPC runtime requires certain restrictions to be placed on the types of objects that can be used as arguments and return values, as described in Section 2.2.1.4.

Service endpoint interface definitions cannot include static fields, and, as a result, constants declared using the usual Java syntax (i.e., using the public static final modifiers) are not allowed.

2.2.1.2 Supported data types

JAX-RPC allows a limited range of data types to be used as method arguments or as the return value. The types for which support is required by the JAX-RPC specification are listed in Table 2-3.

Table 2-3. Java data types that can be used as JAX-RPC method arguments and return values

Data type

Description

Java primitive types

boolean , byte , short , int , long , float , double

Wrapper classes for Java primitive types

Boolean , Byte , Short , Integer , Long , Float , Double

Standard Java classes

The specification requires support for the following:

  • java.lang.String

  • java.util.Calendar

  • java.util.Date

  • java.math.BigDecimal

  • java.math.BigInteger

Value types

Arbitrary classes that meet certain conditions can be used as method arguments and return types. The BookInfo class is an example of a value type. See Section 2.2.1.4 for further details.

Holder classes

Holder classes may be used as method arguments to provide a form of "pass by reference" semantics that is not directly supported by the Java programming language. See Section 2.2.1.3 for further information.

Arrays

Single- and multidimensional arrays, in which the elements are all JAX-RPC-supported types, can be used both as method arguments and for the return value.

Implementations may also provide built-in support for additional types, use of which would, of course, reduce the vendor-independence of a JAX-RPC web service. The reference implementation, for example, allows the use of the following collection classes from the java.util package:

  • ArrayList

  • HashMap

  • HashSet

  • Hashtable

  • LinkedList

  • Properties

  • Stack

  • TreeMap

  • TreeSet

  • Vector

If you need to use data types that are not directly supported by JAX-RPC, you can do so by creating a custom serializer and deserializer that together convert an object of that type to and from its XML representation. However, the current JAX-RPC specification does not provide a framework for creating serializers and deserializers that are portable between different JAX-RPC implementations; therefore, using this feature may compromise your ability to port your service to a different JAX-RPC implementation ”that is, between application servers from different vendors.

Support is not provided for the passing of objects by remote reference in the manner of RMI. Objects used as method arguments and return values are therefore not permitted to implement the java.rmi.Remote interface, which is necessary to implement object-by-reference semantics, since the SOAP 1.1 specification does not provide the support required for true remote method invocation.

There is little difference between the use of a Java primitive type such as int and its object wrapper, so that the following method declarations are both valid:

 public void methodName(int value) throws RemoteException;
public void methodName(Integer value) throws RemoteException; 

However, the second form makes it possible to use null as a distinguished value that might be of special significance to the method being invoked. Similarly, a method that is declared to return a wrapper object can also return a null value. So, for example, if a service interface method were defined to return the number of copies of a named book that a publisher has in stock in the form of an Integer , a return value of null could be reserved to mean that the book title is not recognized, as an alternative to throwing an exception.

Notice that the list of supported types in Table 2-3 does not include java.lang.Object . This means that it is not possible to define an operation with an argument or return type whose runtime type is not specified. This restriction is, however, relaxed in the reference implementation, making it possible (if portability is not an issue) to use a method defined like this:

 public Object sendAnObject(Object arg); 

However, even though arg is declared as being of type Object , its runtime type must still be one of those listed in Table 2-3, or a type for which a custom serializer has been created. The same restriction applies to the return value. When using this feature, it is important to realize that the actual runtime type of the argument used by the sender is not known by the receiver. Hence, in some cases, the object that is delivered to the service implementation class may not be of the same type as the one supplied by the client application. This situation can arise when more than one data type uses the same representation in the XML messages that are exchanged by the client and the service. Consider the following example:

 Object result = stub.sendAnObject(new Date(  )); 

This method call requires that a Date object be transmitted to the server. However, JAX-RPC maps both the Date class and the GregorianCalendar class to the XML schema data type xsd:dateTime , which is what therefore appears in the SOAP message that the client sends. Lacking any specific type information, the server-side JAX-RPC runtime has to choose which representation to use for an xsd:dateTime element in an incoming message. In the reference implementation, it happens to be the case (at least at the time of this writing) that it chooses to create a GregorianCalendar object and not the Date object that was originally supplied. This results in a ClassCastException if the service class implementation assumes that it will receive a Date object. This feature should, therefore, be used only with great caution.

As mentioned earlier, all objects used as method arguments or return values need to have an associated serializer so that they can be converted to and from their XML representations during the method call. JAX-RPC supplies serializers for the data types listed in Table 2-3. Therefore, in most cases, when the method signature specifies the actual object type, the JAX-RPC runtime system can arrange for appropriate serializers to be available to the stubs and ties that are generated from the interface definition. However, when you declare a method that uses Object as an argument or return value, the actual type will not be known until runtime; therefore, the required set of serializers cannot be created from the endpoint interface definition alone. The same is true if the method signature references an abstract class, an interface type, or in the case where a method is declared to use a base class (such as Calendar ) but is actually passed an instance of a derived class (such as GregorianCalendar ). In these examples, the developer must list the actual types that are used at runtime in a configuration file used when the stubs and ties are generated. This topic is covered in more detail in Chapter 6.

Method arguments and return values may be single- or multidimensional arrays of any supported JAX-RPC data type. Note, however, that because a JAX-RPC method call operates on a copy of its argument, assignment to a member of an array does not have the same effect as it would in the case of a local call. Suppose, for example, that you want to define a method in a service endpoint interface that reverses the order of the elements in an array of integers. In the case of a local call, the following code accomplishes this:

 public void reverse(int[] values) {
    for (int i = 0; i < values.length/2; i++) {
        int temp = values[i];
        values[i] = values[values.length - 1 - i];
        values[values.length - 1 - i] = temp;
    }
} 

If this method is called like this:

 int[] values = new int[] {1, 2, 3, 4, 5};
reverse(values); 

then the order of elements in the values array is reversed in-situ , because the reverse( ) method has direct access to the array. If this same method is included in a service endpoint interface and called from a client application, the elements of the array are reversed on the server, but this has no effect on the client's copy. One way to implement this functionality in JAX-RPC is to return the re-ordered array:

 public int[] reverse(int[] values) {
    for (int i = 0; i < values.length/2; i++) {
        int temp = values[i];
        values[i] = values[values.length - 1 - i];
        values[values.length - 1 - i] = temp;
    }
    return values;
} 

and invoke the method like this:

 int[] values = new int[] {1, 2, 3, 4, 5};
int[] reversedValues = reverse(values); 

so that the JAX-RPC runtime returns (a copy of) the server's reversed integer array from the reverse( ) method.

2.2.1.3 Holder classes

Although Java itself does not provide pass-by-reference semantics for method call arguments, JAX-RPC includes a set of holder classes that can be used to simulate something similar to pass-by-reference arguments. The set of standard holder classes, which reside in the javax.xml.rpc.holders package, is shown in the following table:

BigDecimalHolder

BigIntegerHolder

BooleanHolder

BooleanWrapperHolder

ByteArrayHolder

ByteHolder

ByteWrapperHolder

CalendarHolder

DoubleHolder

DoubleWrapperHolder

FloatHolder

FloatWrapperHolder

IntegerWrapperHolder

IntHolder

LongHolder

LongWrapperHolder

ObjectHolder

QNameHolder

ShortHolder

ShortWrapperHolder

StringHolder

Each of the supported JAX-RPC data types has its own holder class. The name of the holder class for a Java primitive type is formed by taking the type name, capitalizing the first letter, and appending Holder so that, for example, an IntHolder is a class that holds a value of type int . The corresponding object wrapper classes use a similar naming convention, except that they append

 WrapperHolder 

instead of Holder . Therefore, the holder class for an object of type java.lang.Integer is called IntegerWrapperHolder .

All of the holder classes implement the javax.rpc.xml.holders.Holder interface. This interface is simply a marker to indicate that classes that implement it are holders ”it does not declare any methods. Instead, holders follow a coding pattern, exemplified by the public API of the IntHolder class , which consists of two constructors and a public field:

 public int value;
public IntHolder(  );
public IntHolder(int value); 

The value associated with the holder is held in the value field, where it can be set before a method call and retrieved when the call completes. The initial value can also be set at construction time. If the no-argument constructor is used, the associated value will be zero, or false for a Boolean value. In the case of an object-value holder such as IntegerWrapperHolder , the default value is null .

Holder classes provide another way to implement the reverse( ) method shown in the previous section. Recall that, since the array of int s passed to this method is passed by copy, we declared the method to return an array of int s so that we could retrieve them in reverse order. In fact, what we really need is to provide copy-by-reference semantics for the integer array, so that the service can (appear to) update it in-place. This is exactly the purpose of a Holder class. However, since there is no standard Holder for an array of integers, we need to define one. The code for this very simple class is shown in Example 2-7.

Example 2-7. A custom holder class for an array of integers
 import javax.xml.rpc.holders.Holder;

/**
 * A class that acts as a holder for an array of integers.
 */
public class IntArrayHolder implements Holder {
    
    // The actual int[] value
    public int[] value;
    
    // Constructs an IntArrayHolder with a null value
    public IntArrayHolder(  ) {
    }
    
    // Constructs an IntArrayHolder initialized with the given array
    public IntArrayHolder(int[] value) {
        this.value = value;
    }
} 

To make use of this holder, replace the reverse( ) method in the service endpoint interface definition with the following:

 public void reverse(IntArrayHolder holder); 

Then, provide the following code in the service implementation:

 public void reverse(IntArrayHolder holder) {
    int[] values = holder.value;
    for (int i = 0; i < values.length/2; i++) {
        int temp = values[i];
        values[i] = values[values.length - 1 - i];
        values[values.length - 1 - i] = temp;
    }
} 

Aside from the change in the method signature, the only difference between this implementation and the original is that the integer array containing the values to reverse is obtained from the values field of the holder, rather than directly as a method argument. Notice that, since the array is updated in-situ, there is no need to change the values field at the end of the method call or to return a value.

The client-side code to invoke this method is also very simple:

 int[] values = new int[] {0, 5, 10, 15, 20};
IntArrayHolder  holder = new IntArrayHolder(values);
bookQuery.reverse(holder);  values = holder.value;   // NOTE THIS LINE (SEE  BELOW)  for (int i = 0; i < values.length; i++) {
    System.out.println(values[i]);
} 

The line of code shown in bold is very important in this example. Although an IntArrayHolder passes an array of integers to the JAX-RPC runtime and allows another array holding the results of the method call to be passed back, the returned array is not actually the one that was originally supplied. The JAX-RPC runtime creates a new array to contain the method call results and assigns it to the value field of the Holder object. This is necessary in the general case because the returned integer array need not contain the same number of elements as the one supplied; therefore, reuse of the original array is not possible.

All of the standard holder classes and our custom IntArrayHolder store the associated value in a public field. When implementing custom holders, it is very important to use the name value for this field, since the stubs generated by the reference implementation use this name to access the content of the holder.

Holder classes are used by the JAX-RPC runtime to represent parameters with "out" or "in-out" semantics (also known as parameter modes ) in code generated from a web service described by a WSDL document. See Chapter 5 for a description of WSDL and Chapter 6 for further discussion of parameter modes.

2.2.1.4 Value types

Although you cannot use arbitrary Java classes as method arguments without writing a custom serializer and deserializer, JAX-RPC does provide support for the use of value types , which are classes having the following characteristics:

  • This class has a public, no-argument constructor. It may also have other constructors for the use of application code, but these will not be used by the JAX-RPC runtime.

  • The class may be derived by extending any other class, may contain static and instance methods, and may implement any Java interfaces apart from java.rmi.Remote or any interface that has java.rmi.Remote as an ancestor .

  • The class may contain static fields and instance fields that are public, protected, package private, or private. Each of these fields must be either another value type (allowing nesting of value types to any depth), a type for which a custom serializer or deserializer is available, or one of the other supported JAX-RPC types listed in Table 2-3.

It might at first appear that almost any Java class could be considered to be a value type. However, the important point about these objects is that, when used as method arguments or a return value, JAX-RPC only copies those parts of the object state that it can get access to, either directly or by using accessor and mutator methods. When a call involving a value-type argument is made, the following steps are taken:

  1. The accessible state of the object is extracted and included as part of the SOAP message sent to the server-side JAX-RPC runtime.

  2. The server-side JAX-RPC runtime creates an instance of the value type using its public, no-argument constructor, then uses the values in the received message to initialize the object state either by writing directly to public fields or using mutator methods for those fields that are not public.

The same process is used when a method has a return value that is a value type object or its argument list includes a Holder class that references one. Therefore, in order for an element of the object state to be transferred from the client to the server, it must satisfy one of the following requirements:

  • It must be a public, nonfinal, and nontransient static or instance field. Clearly, a final field does not need to have its state copied because it is fixed and will be set either when the class is first loaded (for static state) or before or during execution of the constructor. Transient state is not transferred because, by definition, it is not required to be preserved during the process of object transfer by serialization and deserialization.

  • If the field is not public, it must have both an accessor and a mutator method that can be used to retrieve and set its value. These methods must follow the usual JavaBeans naming conventions.

The BookInfo class used in the book web service example and shown in Example 2-4 is an example of a value type. Here, all of the instance fields are private, but their values can be obtained for inclusion in the message sent from the client by calling the accessor methods getTitle( ) , getAuthor( ) , getEditor( ) , and getPrice( ) , and can be set in the object passed to the service implementation using the mutator methods setTitle( ) , setAuthor( ) , setEditor( ) , and setPrice( ) . When the state of the the object is being set from a received message, the order in which the mutator methods are called is undefined; therefore, care should be taken when implementing the value type to ensure that there are no unintended side-effects resulting from the order in which the mutator methods are invoked.

The requirement that the JAX-RPC runtime be able to set these values means that value type classes must have mutator methods, even though this might not be desirable from the point of view of the object model. The BookInfo class is a case in point, since we would prefer that it were an immutable object from the client's viewpoint, but the JAX-RPC runtime requires the presence of mutator methods.

The case in which a class has both a public field and a pair of accessor and mutator methods that correspond to a JavaBeans property that has the same name is considered to be an error:

 public class ValueType {
    public int value;     // Error - name clash. OK if not public.

    public int getValue(  ) {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
} 

In the reference implementation, this error is detected when the stub and tie classes are being generated, and results in an error message from the generation process.

When a value type is derived from another (non- Object ) class, all of the public and nonpublic fields accessible via public accessor and mutator methods, both in the value type class itself and all of the classes in its inheritance hierarchy, are included in the state transferred between the client and the server.

2.2.1.5 Inner classes

The JAX-RPC specification does not mention the subject of inner classes, but the reference implementation supports their use, provided that they are declared as static. For example, suppose you want to extend the book web service so that the BookInfo object contains a type value that indicates whether the subject matter of the book is most closely related to the J2EE, J2SE, or J2ME platform. To do this, you might subclass the value type BookInfo to create a new class called ExtendedBookInfo , for example, that contains the new book type attribute, and then extend the service endpoint interface definition to provide a method that returns instances of the subclass instead of BookInfo itself. Since the book type is a constant that can have only a limited number of fixed values, it is natural to define it as a static inner class of ExtendedBookInfo and then define three static constants that represent the three types. Example 2-8 shows how this might be implemented.

Example 2-8. Using an inner class with JAX-RPC
 public class ExtendedBookInfo extends BookInfo {
    
    // The type of this book
    private BookType bookType;
    
    public ExtendedBookInfo(  ) {
    }
    
    public ExtendedBookInfo(String title, String author, String editor, 
                                double price, BookType bookType) {
        super(title, author, editor, price);
        this.bookType = bookType;
    }
    
    public BookType getBookType(  ) {
        return bookType;
    }
    
    public void setBookType(BookType bookType) {
        this.bookType = bookType;
    }
    
    // Inner class -- must be static and a value type
    public static class BookType {
       
        public static final BookType J2EE_TYPE = new BookType("J2EE");
        public static final BookType J2SE_TYPE = new BookType("J2SE");
        public static final BookType J2ME_TYPE = new BookType("J2ME");

        private String type;
        
        public BookType(  ) {
        }
        
        public BookType(String type) {
            this.type = type;
        }
        
        public String getType(  ) {
            return type;
        }
        
        public void setType(String type) {
            this.type = type;
        }
        
        public String toString(  ) {
            return type;
        }
        
        public int hashCode(  ) {
            return type.hashCode(  );
        }
        
        public boolean equals(Object o) {
            return o instanceof BookType &&
                    type.equals(((BookType)o).type);
        }
    }
} 

Here, BookType is the inner class that represents the type of the book. This class defines three constant values: BookType.J2EE_TYPE , BookType.J2SE_TYPE , and BookType.J2ME_TYPE , which can be passed to the ExtendedBookInfo constructor or its setBookType( ) method to associate the type with a book. The important points to note about this class are:

  • It is a public, static inner class.

  • It is a value type and therefore must have a public, no-argument constructor and accessor and mutator methods for the type property, which is a human-readable description of the type.

This class also overrides the equals( ) and hashCode( ) methods inherited from Object so that expressions like this are possible:

 ExtendedBookInfo info = ....; // Get an instance (not shown)
if (info.getBookType(  ).equals(ExtendedBookInfo.BookType.J2ME_TYPE)) {
    // This is a J2ME book...
} 

Note that the following plausible alternative does not work:

 if (info.getBookType(  ) == ExtendedBookInfo.BookType.J2ME_TYPE) { 

because the value returned by the getBookType( ) method is an instance created by the JAX-RPC runtime on receipt of a reply message and not the constant instance defined in the BookType class. In general, the direct comparison of objects returned by JAX-RPC method calls should be avoided and equals( ) used instead.

2.2.1.6 Other data types

In addition to those already covered in this section, JAX-RPC provides mechanisms for the use of two other classes of data types:

  • Data that can be represented using a MIME encoding. Objects of this type are carried as an attachment in the SOAP messages that implement the remote procedure call.

  • XML documents or fragments of XML documents. Arbitrary XML fragments can be passed as method arguments or as a method return value using classes provided by the SAAJ API, which is covered in Chapter 3.

The JAX-RPC API that allows these data types to be used is discussed in Chapter 6.

2.2.2 Converting the Service Interface Definition to Client-Side Stubs

The JAX-RPC specification does not specify how service endpoint interface definitions are converted into the stubs and ties required by the JAX-RPC runtime. It also doesn't place any constraints on how the stubs and ties operate . Both the conversion mechanism and the resulting classes are, therefore, entirely dependent on the JAX-RPC implementation that you use. We have already seen (in Section 2.1.2.3 earlier in this chapter) that the client code is dependent on the names of the generated stub classes. Although the design of portable stubs is an aim for a future revision of the specification, at the present time, if you want to change your JAX-RPC software vendor, you need to regenerate the stubs (for the client application) and modify your client code. In this section, we assume that you are using the JAX-RPC reference implementation. [10]

[10] As well as being vendor-dependent , stubs and ties are specific to the underlying messaging service and transport protocol in use. The stubs created by the reference implementation work only for SOAP 1.1 messages carried by HTTP 1.1. If support is provided at some future point for alternatives, then using a different communications infrastructure would involve creating and linking different stubs and ties, even if the same JAX-RPC implementation is used.

The reference implementation provides a command-line utility called wscompile that, given either a WSDL file or a service endpoint interface definition written in Java, can generate the following:

  • The compiled class files and, optionally , the Java source files, for the stubs required to interface with the reference implementation's client-side JAX-RPC runtime.

  • The compiled class files and, optionally, the corresponding source files, for the ties required by the JAX-RPC server-side runtime, together with configuration information that is used to link these artifacts to the web or EJB container that will dispatch incoming service requests . In practice, however, wscompile is usually not used directly to create these artifacts. Instead, for the J2EE 1.4 platform, they are created using either the deploytool or j2eec utilities, whereas the JWSDP provides a different utility called wsdeploy . For further information on generating ties, refer to Section 2.2.4.

  • If the service is presented as a Java interface (as is the case in this chapter), a WSDL file that is equivalent to the interface definition.

  • If the service is presented in the form of a WSDL document (as will be the case for some of the examples in Chapter 6), the Java interface definition that corresponds to the service endpoint that it defines, together with Java classes for any value-type objects that it references.

  • A model file that describes the service in an internal form that can be processed more quickly than either compiled Java class files or WSDL documents. Having created a model file using wscompile , you can use it next time you run wscompile for improved performance. Model files can also be used when creating the server-side artifacts, as you'll see later.

The wscompile utility has many command-line arguments, which are described in detail in Chapter 8. You need only to use a small number of these to create the client-side stubs. The following command line generates the stubs, compiles them, and places them below the directory output , which must already exist:

 wscompile -gen:client -d  output/client -classpath   classpath config-file   

The classpath argument is a colon -separated (for Unix) or semicolon-separated (for Windows) list of JAR files or directories that contain the class file for the endpoint interface definition for which the stubs are to be generated and any supporting classes on which they depend. In most cases, this argument contains a single directory name, since it is desirable to keep all of the classes that make up the interface in the same package. The actual name of the class that represents the endpoint interface is specified in the configuration file given as the last argument to wscompile and described next.

The command line just shown creates the source files for the stubs in a temporary location and deletes them once they are compiled. You can arrange to retain the Java source files for inspection by using the -keep argument and supplying the name of the directory below which they will be placed (which must already exist), using the -s argument:

 wscompile -gen:client -keep -s  generated/client  -d  output/client 
    -classpath   classpath config-file   

The Ant buildfile for the example shown in this chapter provides a target called generate-client that runs wscompile to create the client stubs and retains the Java source files for inspection so that you can get a feel for how they work. The generated source files and the class files are stored in directories that make clear that they are client-side artifacts. The pathnames of these directories, relative to the installation directory of the example source code, are:

Java source code

chapter2\bookservice\generated\client

Compiled class files

chapter2\bookservice\output\client

The format of the configuration file required by wscompile depends on whether it is being given a WSDL document, a model file, or a service defined by Java interfaces as its starting point. You'll find a complete specification of this file in Chapter 8. The configuration information for the book web service can be found in the file chapter2\bookservice\config.xml . The content of this file is shown in Example 2-9, in which the line numbers shown on the left are for reference purposes only and do not exist in the file itself. [11]

[11] The use of the name config.xml for this file is consistent with the naming conventions used by the documentation supplied with the JWSDP, but it is only a convention. You can choose any name you like for this file.

Example 2-9. wscompile configuration file for the book web service
 1    <?xml version="1.0" encoding="UTF-8" ?>
2    <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
3        <service name="BookService" 
4            targetNamespace="urn:jwsnut.chapter2.bookservice/wsdl/BookQuery"
5            typeNamespace="urn:jwsnut.chapter2.bookservice/types/BookQuery"
6            packageName="ora.jwsnut.chapter2.bookservice"> 
7                        
8           <interface name="ora.jwsnut.chapter2.bookservice.BookQuery"/>
9        </service> 
10   </configuration> 

The outermost element of this file, called configuration , specifies that this is a wscompile configuration file, the format of which is defined by the XML schema document that can be found at the URL http://java.sun.com/xml/ns/jax-rpc/ri/config. If you want to look at this document offline and have installed the JWSDP tutorial, you'll find a copy in the file docs\tutorial\examples\ jaxrpc \common\jax-rpc-ri-config.xsd . If you are not familiar with XML schema documents, you can find a tutorial at http://www.w3c.org/TR/xmlschema-0, or else pick up a copy of XML Schema , by Eric van der Vlist (O'Reilly). Since XML schema files and the data types defined by the W3C XML schema documents are used extensively by JAX-RPC, it is advantageous to have at least some familiarity with this subject.

The configuration element may contain one of the following nested elements:

service

Defines a web service in terms of Java interface definitions

wsdl

Defines a web service using a WSDL document

modelfile

Defines a web service from a model file

In this chapter, we look only at the use of the service element, which has four associated attributes that are listed in Table 2-4. Typical values for these attributes are shown in Example 2-9.

Table 2-4. Attributes of the config.xml service element

Attribute name

Description

name

The name to be used for the web service. This value of this attribute determines the names of some of the generated files, including the Java source file that represents the service itself. By convention, this name is also part of the URL used to reference the web service and, in the example code used in this book, it is also the name of the WAR file used to deploy the server-side components .

targetNamespace

The XML namespace that will be used in the generated WSDL file for the names associated with the service itself, the port type (i.e., the endpoint interface definition), the operations (i.e., the methods), and the definitions of the SOAP messages exchanged by the client and service JAX-RPC runtimes . This namespace also appears in the code that is generated by wscompile . It is important that the value of this attribute is set correctly ” see Section 2.2.7.5 at the end of this chapter for more information.

typeNamespace

This is the XML namespace that will be used in the generated WSDL file for any data types that are declared by the web service definition. In the book service example, BookInfo is an example of a data type that would be associated with this namespace. It is important that the value of this attribute is set correctly ” see Section 2.2.7.5 at the end of this chapter for more information.

packageName

The name of the Java package in which the classes generated by wscompile for the service itself will be placed (see Table 2-5). The stub classes generated from the endpoint interface definition appear in the same package as the service endpoint interface and are therefore not affected by this setting. In the book web service example, we use ora.jwsnut.chapter2.bookservice , which is the same package used by the endpoint interfaces; therefore, all of the generated classes appear in the same package.

The service element must have a nested interface element. An interface element may have several attributes, of which only the name attribute is mandatory. [12] This attribute gives the the fully qualified name of the Java class that contains the service endpoint interface definition, and, in the case of the web service, it has the value ora.jwsnut.chapter2.bookservice.BookQuery .

[12] Refer to Chapter 8 for a more complete desccription of the elements and attributes in the wscompile configuration file.

Using this book's example source code, type the following command:

 ant generate-client 

This runs wscompile to generate the client-side stubs, writing the Java source files to the directory chapter2\bookservice\generated\client and the compiled class files to chapter2\bookservice\output\client . As noted before, we deliberately place client-related files in a separate directory hierarchy from those related to the interface definition and the service implementation so that you can easily see which files relate to which part. Below these directories, the files are arranged according to their package location. Since the interface class (shown in Example 2-3) is in the package ora.jwsnut.chapter2.bookservice and the packageName attribute in the config.xml file (shown on line 6 of Example 2-9) has the same value, all of the generated source files will be in the directory chapter2\bookservice\generated\client\ora\jwsnut\chapter2\bookservice , while the compiled classes will be written to chapter2\bookservice\output\client\ora\jwsnut\chapter2\bookservice . If you examine the set of files created, you will find that there are several different groupings, a selection of which are shown in Table 2-5.

Table 2-5. Some of the client-side source files generated by wscompile

Source

Generated files

Service

BookService.java

 

BookService_Impl.java

 

BookService_SerializerRegistry.java

Exception

BookServiceException_SOAPSerializer.java

 

BookServiceException_SOAPBuilder.java

Value type

BookInfo_SOAPSerializer.java

 

BookInfo_SOAPBuilder.java

BookQuery interface

BookQuery_Stub.java

 

BookQuery_getAuthor_RequestStruct.java

 

BookQuery_GetAuthor_ResponseStruct.java

 

BookQuery_getAuthor_RequestStruct_SOAPBuilder.java

 

BookQuery_GetAuthor_ResponseStruct_SOAPBuilder.java

 

BookQuery_getAuthor_RequestStruct_SOAPSerializer.java

 

BookQuery_GetAuthor_ResponseStruct_SOAPSerializer.java

 

BookQuery_getBookCount_RequestStruct.java

 

BookQuery_GetBookCount_ResponseStruct.java

 

BookQuery_getBookCount_RequestStruct_SOAPSerializer.java

 

BookQuery_GetBookCount_ResponseStruct_SOAPSerializer.java

If you choose a different package name for the packageName attribute in the config.xml file, only the three service-related files shown in Table 2-5 will be generated in that package. The package location for all of the other files is determined by the package lines in the Java interface definitions themselves .

2.2.3 The Client-Side JAX-RPC API

Most of the files in Table 2-5 handle the details of interfacing with the JAX-RPC runtime to convert client method calls to SOAP messages and extract the return value, if there is one, from the reply message. To use the client-side JAX-RPC API, you need only to concern yourself with the service-related files and the stubs.

The file BookService.java contains the definition of an interface that represents the book web service itself. In other words, it corresponds directly to the service element in the wscompile configuration file. The content of this file is shown in Example 2-10.

Example 2-10. The BookService.java file, generated by xrpcc
 package ora.jwsnut.chapter2.bookservice;

import javax.xml.rpc.*;

public interface BookService extends javax.xml.rpc.Service {
    public ora.jwsnut.chapter2.bookservice.BookQuery getBookQueryPort(  );
} 

The BookService interface extends javax.xml.rpc.Service , which, despite its name, is actually a JAX-RPC client-side interface. Most of the methods of the Service interface are concerned with the details of the dynamic invocation interface (DII), which will be covered in Chapter 6. The single generated method in the BookService interface allows you to get a reference to the stub object for the endpoint interface for this service. The value returned by the getBookQueryPort( ) method is of type ora.jwsnut.chapter2.bookservice.BookQuery , which corresponds to the service endpoint interface itself, rather than the actual runtime type of the generated stub. This makes the code that uses the stub more portable, since it does not need to refer to the stub class using an implementation-dependent name.

When writing an application, you need to get a reference to the stub. In order to get one, you need an object that implements the BookService interface so that you can call its getBookQueryPort( ) method. The only way to get such an object is to instantiate BookService_Impl , which is another class generated by wscompile that implements the BookService interface. This is unfortunate, because the JAX-RPC specification does not require the name of the implementation class for the service interface to be formed by adding _Impl to the service interface name as it is in this case ”it recommends only that it should be. As a result, the code required to access the service is dependent on a particular JAX-RPC implementation:

 // Get a reference to the stub and set the service address
BookService_Impl  service = new BookService_Impl(  ); 

The client-side JAX-RPC API includes an interface called ServiceFactory , which is intended to allow Service objects to be created without requiring prior knowledge of the implementation class involved. Ideally, it should be possible to obtain a Service object by calling a method of ServiceFactory , passing it the name of the service that you require. Unfortunately, this works only if your service is described by a WSDL document, or if you want to use the dynamic invocation interface; ServiceFactory is of no use when using statically created client-side stubs. The ServiceFactory interface is described in Chapter 6.

Given an instance of the Service implementation class, you can get a reference to an object that implements the service endpoint interface:

 BookQuery  bookQuery = (BookQuery)service.getBookQueryPort(  ); 

Using this reference, you can call any of the methods of the BookQuery interface:

 String  editorName = bookQuery.getEditor(bookTitle); 

However, this does not work yet, because nowhere have you told the JAX-RPC runtime how to connect to the host containing the service implementation. In order to do this, you have to configure the stub. As well as implementing the methods of service endpoint interface, the stub returned by the getBookQueryPort( ) method implements the interface javax.xml.rpc.Stub , which is part of the client-side JAX-RPC API. [13] This interface is particularly simple, consisting of only three methods:

[13] In fact, the object returned by the getBookQueryPort( ) method is an instance of the generated BookQuery_Stub class that was listed in Table 2-5.

 public interface Stub {
    public abstract Object _getProperty(String name) throws JAXRPCException;
    public abstract Iterator  _getPropertyNames(  );
    public abstract void  _setProperty(String name, Object value) 
         throws JAXRPCException;
} 

These methods allow a Stub to be configured using a set of properties, a small number of which are defined by the JAX-RPC specification and are therefore portable across different JAX-RPC implementations. Vendors are also permitted to define their own properties, which the application code may use at the risk of reducing application portability. The names of the standard properties, which are described in Table 2-6, are all constants defined by the Stub interface.

Table 2-6. Standard Stub properties

Property name

Type

Description

ENDPOINT_ADDRESS_PROPERTY

String

The address of the service to which the stub should connect. The format of this address depends on the protocol used to carry the messages to the server.

SESSION_MAINTAIN_PROPERTY

Boolean

Specifies whether the client wishes to enter into and maintain a session with the service endpoint. By default, this property is false and session management is not performed. Refer to Section 6.7 in Chapter 6 for more information on the use of this property.

USERNAME_PROPERTY

PASSWORD_PROPERTY

String

These properties can be used to specify a username and password if the server requires client authentication. Support for basic HTTP authentication is required by the specification when HTTP is used as the underlying message transport mechanism. Authentication is described in more detail in Section 6.7.6.

The _getPropertyNames( ) method returns an Iterator whose values are the names of the properties for which the stub has configured values. The _getProperty( ) method returns the value associated with a single named property, while _setProperty( ) changes a given property's value. The last two methods throw a JAXRPCException if they detect an error, which might be caused by one of the following:

  • Using an invalid property name.

  • Attempting to associate a value with a property that is not of the type expected by that property.

  • Attempting to associate a value with a property that is of the correct type but is illegal for some other reason. Note, however, that not all property values are necessarily verified at the time that they are set. For example, the value of the ENDPOINT_ADDRESS_PROPERTY might not be checked by the _setProperty( ) method because its validity can only be determined by attempting to use it. In this case, an illegal value instead leads to an exception during the invocation of a remote method.

JAXRPCException is defined in the javax.xml.rpc package. It may have an associated error message and/or refer to another Throwable that describes the initial cause of an error. These values can be obtained using the getMessage( ) and getLinkedCause( ) methods, respectively. Since JAXRPCException is a RuntimeException , application code is not obliged to catch and handle it.

The address of the service to which the Stub should connect is configured by setting the property ENDPOINT_ADDRESS_PROPERTY , the value of which must be a String . The way in which the address is interpreted depends on the transport mechanism and the implementation of the server-side JAX-RPC runtime. The book web service application client avoids any knowledge of the format of the address by obtaining it from the command line and then calling the _setProperty( ) method of the Stub object obtained from the getBookQueryPort( ) method to configure it:

 ((Stub)bookQuery)._setProperty(Stub.ENDPOINT_ADDRESS_PROPERTY, args[0]); 

You'll see how the appropriate value for the address is actually constructed in the next section.

2.2.4 The Server-Side Implementation

As you saw earlier in this chapter, the server-side implementation of the book web service is not at all dependent on JAX-RPC ” it is simply a set of classes, one of which implements the methods of the BookQuery interface. In fact, there really isn't very much that can be termed a server-side JAX-RPC API. The javax.xml.rpc.server package, which contains what API there is, consists only of two interfaces, both of which deal with accessing the servlet environment from which the methods of the servant class will be invoked. Neither of these interfaces is relevant to a web service hosted by an EJB, so there really is no service-side JAX-RPC API at all for EJB-hosted services. Since the book web service (which is initially implemented within a servlet) doesn't need to access the servlet environment, we defer discussion of the javax.xml.rpc.server package to Chapter 6.

2.2.4.1 Server-side architecture

In order to understand how a JAX-RPC service is deployed, it is useful to review how the methods of the service implementation class are invoked by the server-side JAX-RPC runtime. Figure 2-10 shows the server-side architecture for the case in which the service is deployed in a web container. The alternative, in which the service is provided by a stateless session bean, is covered separately in Section 2.3 later in this chapter.

Figure 2-10. The server-side JAX-RPC runtime environment as implemented in a web container
figs/jwsn_0210.gif

SOAP messages for a service deployed in a web container are handled first by a servlet that is registered to receive HTTP requests directed to URLs that begin with the context path of the web application in which the service is deployed, plus an additional part that identifies the web service itself. The way in which these URL parts are determined depends on the details of the deployment, which are described in Section 2.2.5. For example, the URL for the book web service, when deployed on the J2EE 1.4 platform using deployment information supplied with the example source code, is http://localhost:8000/Books/BookQuery . Here, the context path is Books and the part of the URL that corresponds to the book web service itself is BookQuery . In terms of Figure 2-10, the entire URL maps to the single servlet instance shown at the top-left of the diagram, within the Books web application.

On receipt of a SOAP message, the servlet locates the generated tie class for the service endpoint interface and passes it the message, from which the tie extracts the name of the method to be invoked, together with the XML-encoded representations of its parameters. The tie decodes the method parameters and uses them to invoke the target method of the servant class. The return value and any output parameters are then encoded into XML and used to build a reply message that the servlet returns to the calling client.

The JAX-RPC specification does not describe in detail how the servlet itself is to be implemented. Vendors are free to provide their own implementation or may generate one during the deployment process. The information that determines the URL of the servlet is provided at deployment time, along with details that allow the servlet to find the service endpoint interface, the tie class, and the service implementation class. The information required to deploy a web service on the J2EE 1.4 platform is defined by the J2EE Web Services specification (JSR 109). There is no formal specification that covers other possible deployment targets (such as the Tomcat web container with the JWSDP, which defines its own set of deployment files as described in Section 2.2.7).

2.2.4.2 Service implementation threading model

If a servlet receives a client request for a web service while that service is already handling an earlier request, it needs to decide whether to dispatch the new request immediately or to defer it until handling of the existing request is completed. The JAX-RPC specification does not define what should happen in this case, but there is a precise definition for the case in which the web service is deployed on the J2EE 1.4 platform. The rules are as follows:

Service hosted by a servlet on the J2EE 1.4 platform

In this case, the behavior depends on whether the servant class implements the javax.servlet.SingleThreadModel interface. If it does not, the servlet may dispatch the request immediately to an existing instance of the servant class. As a result, there may be multiple threads of execution within the servant class at any given time, and it must therefore be implemented in a thread-safe manner. On the other hand, if the servant class implements SingleThreadModel , the servlet ensures that only one thread of execution is active in any one instance of the servant class. It may do this by serializing all requests through a single instance of the class or by creating as many instances as are necessary to service all outstanding requests (probably subject to a maximum number of instances) and dispatch each request to a dedicated instance.

Service hosted by a stateless session bean on the J2EE 1.4 platform

Like all EJBs, stateless session beans are inherently single-threaded, and therefore the container must ensure that only one web service request at a time is active within any given EJB. Should another request be received while an earlier one is active, the container may choose to create a new instance of the bean to service the request, or may queue the request for later processing by an existing instance.

Service hosted by the JWSDP in a Tomcat web container

The JWSDP behaves as if the servant class did not implement the SingleThreadModel interface. In other words, all requests are dispatched to a single servant instance, which must therefore be implemented so that it is thread-safe.

An important point to note is that the servant class, whether it is hosted in a servlet or a stateless session bean, cannot hold client-specific state in its instance variables . Attempting to do so would lead to undefined effects, since the same instance might be used first to service a method call from one client and then to handle a call from another client, thereby changing the state created for the first client. Alternatively, if the server-side implementation adopts a pooled model, two successive method calls from the same client might be dispatched to different instances of the servant class, which would also result in the client state set up by the first method called being unavailable for the second call. The bottom line is that all operations in web service interfaces must supply all the necessary information as method parameters, and the operation cannot have any side effects. [14]

[14] In fact, as we'll see in Chapter 6, it is possible to use HTTP sessions to pass client-specific state between a JAX-RPC client and the servant class, provided that the service is hosted in a web container. There is no such facility for EJB-hosted web services.

2.2.5 Deployment of a JAX-RPC Service in a Web Container

To deploy a servlet-hosted web service, create a web archive (WAR) file containing the classes and resources required to define a J2EE web application. [15] The files that you might expect to have to place in this archive include:

[15] The deployment of a web service hosted by an EJB is described separately in Section 2.3 later in this chapter.

  • The Java class file for the service endpoint interface

  • The Java class files for the service implementation and any resources that it relies on, such as the booklist.txt file

  • A web.xml file that contains deployment information for the web application

  • The class files for the JAX-RPC tie classes

The responsibilty for creating the files in the first three categories lies with the developer. However, the generated tie classes are dependent on a specific JAX-RPC implementation, so, if you create a WAR file that includs them, you would only be able to deploy the file into a container hosting the JAX-RPC implementation used to generate them. In order to clearly separate the duties of the application developer or assembler from those of the person performing the actual deployment, the process is actually divided into two steps:

  1. The developer or application assembler creates an archive that contains the components that have no dependency on the JAX-RPC runtime. The result of this step is a portable WAR file.

  2. The deployer processes the portable WAR file to create a separate, implementation-specific archive or archives that can be deployed into a specific web container. This processing is performed by a tool provided by the vendor of the target JAX-RPC environment.

The details of both of these steps depend on whether your deployment target is the J2EE 1.4 reference implementation (or a commercial product derived from it) or a Tomcat web container hosting the JWSDP. These two cases are described separately in the following sections.

In the case of the book web service, the content of the portable WAR file includes those files listed in Table 2-7, which are clearly not dependent on the deployment platform.

Table 2-7. Content of the portable WAR file for the book web service

Category

Filename

Service Definition

WEB-INF/classes/ora/jwsnut/chapter2/bookservice/BookQuery.class

 

WEB-INF/classes/ora/jwsnut/chapter2/bookservice/BookServiceException.class

 

WEB-INF/classes/ora/jwsnut/chapter2/bookservice/BookInfo.class

Implementation

WEB-INF/classes/ora/jwsnut/chapter2/bookservice/BookServiceServant.class

 

WEB-INF/classes/ora/jwsnut/chapter2/bookservice/BookServiceServantData.class

Resources

WEB-INF/classes/ora/jwsnut/chapter2/bookservice/booklist.txt

In addition, the developer is required to include one or more files that contain information required by the tools that will perform the actual deployment. The requirement to include these files appears to make the archive nonportable. However, this is not strictly true, for the following reasons:

  • A portable archive that will eventually be deployed onto the J2EE 1.4 platform requires a set of files that are defined by the J2EE Web Service specification. Such an archive is, therefore, portable amongst all implementations of that specification. In practice, additional deployment information will likely need to be supplied in vendor-specific files within the archive. As long as these are named differently by each vendor, it should still be possible to create a single portable archive that can be targeted at a range of J2EE 1.4 platform implementations by including the additional vendor-specific files for all of them.

  • The JWSDP also requires additional deployment information. Unfortunately, at the time of this writing, the JWSDP requires the developer to include in the portable WAR file a version of the web.xml file that is not compatible with the requirements placed on the same file by J2EE 1.4. It is therefore not practical to build a portable web archive that can be deployed both to a J2EE 1.4 platform and to a web container hosting the JWSDP reference implementation. In practice, it is unlikely that the JWSDP will be used as a deployment target in production environments; therefore, the incompatibility with J2EE 1.4 is not really important. [16]

    [16] It happens to be the case that, despite the apparent incompatibility between J2EE 1.4 and the JWSDP, it is possible to create a single web.xml file that can be used for both deployments; therefore, a single portable WAR file can be built that is suitable for both. However, after processing, the web.xml file in the deployable WAR file for the JWSDP contains content that should not, strictly speaking, be legal, even though it happens to work when deployed to the Tomcat web container. This should be considered good luck and should not be relied upon in the real world.

As a result of the differences between these two cases, the deployment targets in the Ant buildfiles for the example source code for this book can be used to build a portable WAR file that can be deployed either to J2EE 1.4 or to the JWSDP (but not to both). The two deployment processes are described separately in the next two sections.

2.2.6 Deploying a JAX-RPC Service onto the J2EE 1.4 Platform

Deployment of a JAX-RPC web service onto the J2EE 1.4 platform requires the creation of a portable web archive and processing of that archive by utilities provided by the target platform. This section describes the steps required to deploy a service using the tools provided by this J2EE 1.4 reference implementation.

2.2.6.1 Creating the portable WAR file

The portable WAR file for the J2EE 1.4 platform requires the files listed in Table 2-7, plus a number of other files that contain deployment information. The content of these files is completely described by the J2EE Web Services specification, so you can rely on the fact that the same files should work with all conforming implementations. Vendors are, however, free to require additional information to be provided in files that are specific to their own implementations of the J2EE 1.4 platform. To create the deployable WAR file for the book web service, use the command:

 ant portable-web-package 

which results in an archive called Books-portable.war being created in the directory chapter2\bookservice .

The required deployment files are listed in Table 2-8.

Table 2-8. Deployment files required by the J2EE 1.4 platform

File

Description

WEB-INF/web.xml

Maps web application URLs to the servlets hosting the web service and contains environment settings and parameters for those servlets, together with security constraints that may be used to restrict access only to authorized users.

WEB-INF/ webservices .xml

Describes the web services contained in the archive and how they map to servlets or EJBs.

WSDL files

Definitions for each web service in the webservices.xml file.

Mapping file

Defines the mapping from the WSDL definition of the web services to the Java classes that implement the service.

The easiest way to describe these files is to look at those used to deploy the book web service. The web.xml file for this service is shown in Example 2-11 and a copy of it, together with the other deployment files, can be found in the directory chapter2\bookservice\deploy-j2ee14 relative to the book's example source code.

Example 2-11. The web.xml file for the book web service for deployment on the J2EE 1.4 platform
 <?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">

<web-app>
  <display-name>JAX-RPC Book Service</display-name>
  <description>JAX-RPC Book Service</description>
  <servlet>
    <servlet-name>BookQueryServlet</servlet-name>
    <servlet-class>ora.jwsnut.chapter2.bookservice.BookServiceServant
         </servlet-class>
    <load-on-startup>0</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>BookQueryServlet</servlet-name>
    <url-pattern>/BookQuery</url-pattern>
  </servlet-mapping>
  <session-config>
    <session-timeout>60</session-timeout>
  </session-config>
</web-app> 

This is a standard web.xml file that declares a single servlet called BookQueryServlet , which can be accessed using the path BookQuery relative to the context path of the web application itself. If the web application is deployed with the context path Books , then the URL http://localhost:8000/Books/BookQuery can be used to send SOAP messages to this web service. The following rules apply to the servlet-mapping element in the web.xml file when the servlet it relates to is the host for a J2EE web service.

  • It is not mandatory to include a servlet-mapping for the servlet. If this element is omitted, the deployment tools will determine a suitable mapping and include the appropriate servlet-mapping element in the deployed version of the web.xml file. [17]

    [17] Although this requirement appears in the J2EE Web Services specification, at the time of this writing it is not implemented in the J2EE 1.4 reference implementation.

  • If a servlet-mapping is specified, then the url-pattern must not be a wildcard path. In other words, values such as /* and /BookQuery* are not valid.

  • Only one servlet-mapping may be specified for each servlet that hosts a web service.

Once the service is deployed, its URL is used to update the address element in the WSDL definition so that clients can use this file to locate the web service. See Chapter 5 for a discussion of WSDL files and the addressing information that they contain.

The servlet name assigned using the servlet-name tag should be unique within the web archive. It is used to link the servlet to the ports in the webservices.xml file (shown later) for the service endpoint interfaces that it hosts. The servlet-class element contains the name of the service implementation class rather than a servlet class. At deployment time, this name is replaced by a reference to a servlet provided or generated by the target container to create a valid web.xml file.

The portable WAR file must include a WSDL file that describes the web service being deployed. The book service example used in this chapter is specified using a Java interface definition, so a WSDL description is not immediately available. However, you can use the command-line utility wscompile to create a suitable WSDL file, using a command line like the following:

 wscompile -define -classpath output\interface -d output\server config.xml 

where the config.xml file is as shown in Example 2-9, and gives the name of the service and the class name of the Java interface that defines the service endpoint. The -classpath option points to the directory beneath which the compiled interface class file can be found. The WSDL file is written to the directory supplied with the -d option and is typically copied to the root directory or the WEB-INF directory of the portable archive. The exact location of this file within the archive is not critical because it is specified in the webservices.xml file.

The webservices.xml file contains definitions for the web services that appear in the portable WAR file. The content of this file for the book service is shown in Example 2-12. This file must reside in the WEB-INF directory of the archive. For a complete description of the elements that may appear in this file, refer to Chapter 8.

Example 2-12. The webservices.xml file for the book web service
 <?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE webservices 
   PUBLIC "-//IBM Corporation, Inc.//DTD J2EE Web services 1.0//EN" 
   "http://www.ibm.com/standards/xml/webservices/j2ee/j2ee_web_services_1_0.dtd">
         
<webservices>
  <webservice-description>
    <webservice-description-name>JAX-RPC Book Service
    </webservice-description-name>
    <wsdl-file>BookService.wsdl</wsdl-file>
    <jaxrpc-mapping-file>WEB-INF/model</jaxrpc-mapping-file>
    <port-component>
      <port-component-name>BookQueryPort</port-component-name>
      <wsdl-port>
        <namespaceURI>urn:jwsnut.chapter2.bookservice/wsdl/BookQuery
        </namespaceURI>
        <localpart>BookQueryPort</localpart>
      </wsdl-port>
      <service-endpoint-interface>ora.jwsnut.chapter2.bookservice.BookQuery
      </service-endpoint-interface>
      <service-impl-bean>
        <servlet-link>BookQueryServlet</servlet-link>
      </service-impl-bean>
    </port-component>
  </webservice-description>
</webservices> 

A WAR file may contain implementations of any number of web services; for each, there is a corresponding webservice-description element in the webservices.xml file. Each such element must contain at least the following child elements:

wsdl-file

Points to the WSDL document that describes the web service represented by the containing webservice-description element. The value of this element is a path that is taken to be relative to the root of the archive. There is no fixed location for this file. In Example 2-12, the WSDL definition is in a file called BookService.wsdl located in the root of the archive. It is not necessary to include a leading " / " to indicate that the path begins with the archive root.

jaxrpc-mapping-file

A file that describes a mapping between the WSDL definition and the Java interfaces that represent the service endpoints of the web service. There is no fixed name or location within the web archive for this file. Its content is used by the deployment tools when creating the tie and serializer classes for the deployed web service. This is a complex file that is described in more detail in Chapter 8. In the simplest cases, it is only necessary to indicate how the namespaces within the WSDL file for the service are mapped to Java packages. The J2EE 1.4 reference implementation allows a model file to be supplied instead of a JAX-RPC mapping file. This is convenient, because a model file can be generated automatically from either a Java interface definition or a WSDL file. The examples in this book all take advantage of this feature in order to minimize the work required to deploy a web service on the J2EE 1.4 platform. Vendor implementations are not required to support this, but are likely to provide a tool that will allow a default mapping file to be created with minimal effort. In Example 2-12, the jaxrpc-mapping-file element points to a model file held within the WEB-INF directory of the archive.

port-component

A webservice-description element must contain a port-component element for each port in its associated WSDL file. A mismatch between the set of ports in the WSDL file and the port-component elements in webservices.xml causes an error at deployment time. Recall from the beginning of this chapter that a port represents a binding of a service endpoint interface to a particular combination of messaging and transport protocols at a specific transport address. The child elements of the port-component element contain the information necessary to identify the WSDL port and the way in which it is implemented within the web container.

The port-component element has several optional child elements that allow the inclusion of descriptive information, plus an optional handler element that allows SOAP message handlers to be configured. (SOAP message handlers are an advanced feature that is described in Chapter 6.) The following child elements must appear exactly once, in the following order, in each port-component element:

port-component-name

Provides a name for the port. This name is not necessarily related to the name port that appears within the WSDL definition for the web service, but it must be unique among all port-component elements in the webservices.xml file.

wsdl-port

Specifies the port within the WSDL definition that the port-component is associated with, in terms of the port name and its namespace URI (refer to Example 2-12). The quickest way to determine the values to be used within this element is to locate the definition of the port in the WSDL file and transcribe the name and namespace URI from there.

service-endpoint-interface

Gives the fully qualified Java class name of the class that represents the service endpoint interface.

service-impl-bean

This element is the link from the port definition to its actual implementation. The service-impl-bean element must contain either a nested servlet-link or a nested ejb-link element, depending on whether the service is hosted within a servlet or a stateless session bean. In the former case, the value of the servlet-link element is the servlet-name of a servlet defined in the web.xml file in the same web archive. In Example 2-12, for instance, this element has the value BookQueryServlet , which indicates that the web service is hosted by the servlet of the name defined in the web.xml file shown in Example 2-11. This, in turn , means that the URL associated with this port will be /BookQuery relative to the context path of the web application. For a description of the alternative ejb-link element, refer to Section 2.3, later in this chapter.

Note that the J2EE Web Services specification requires that only one port-component element be associated with any particular servlet, which means that one servlet can only implement a single service endpoint interface.

2.2.6.2 Deploying the JAX-RPC service

Once you have a portable WAR file, there are several ways to deploy it to a J2EE application server. The most direct approach is to use the -deployModule option of the deploytool utility:

 deploytool -server localhost -deployModule -id Books Books-portable.war 

This command causes the web service in the file Books-portable.war to be deployed on the application server running on localhost . During the deployment, the appropriate tie classes are generated and the linkage between the servlet, the tie classes, and the web service implementation class (as shown in Figure 2-10) are created. The id argument supplies the context path of the web application hosting the web service, so that the BookQuery port of the service is available at the URL http://localhost:8000/Books/BookQuery . This method also works if the portable WAR file is wrapped into an Enterprise Archive (EAR) in order to allow EJBs to be deployed at the same time.

As an alternative, you can perform the deployment in two stages. The first step uses the j2eec utility to create the ties required by the web service and saves them in a separate JAR file, along with the appropriate serializers, the client-side stubs, and the generated Service class for the service:

 j2eec -o Books-generated.jar Books-portable.war 

The second step uses the deploytool -deployGeneratedModule option to complete the deployment:

 deploytool -server localhost -deployGeneratedModule -id Books 
Books-portable.war Books-generated.jar 

The -deployGeneratedModule option differs from -deployModule in that it requires a JAR file containing the pregenerated ties to be supplied as a command-line argument, whereas the -deployModule option generates the ties each time it is used. During development, it may be faster to use the -deployGeneratedModule option because it avoids the overhead of tie class generation for each deployment, provided that you don't change the web service interface in any way that would require a change in the generated classes.

2.2.7 Deploying a JAX-RPC Service with the JWSDP

This section describes the steps needed to deploy a JAX-RPC web service into a web container hosting the JWSDP reference implementation. Before attempting to deploy the book web service into the JWSDP, you should edit the jwsnutExamples.properties file in your home directory so that the USING_JWSDP property is set, as described in Examples Online in the Preface. These settings determine how the Ant buildfile targets perform the deployment.

2.2.7.1 Creating the portable WAR file

As noted earlier, the portable WAR file for the JWSDP is portable only between implementations of the JWSDP. To create a portable WAR file for the book web service, with chapter2\bookservice as your working directory, type the command:

 ant portable-web-package 

which creates a file called Books-portable.war in the same directory. If you look at the content of this file, you'll find that it contains the three files shown in Table 2-9 in addition to those listed in Table 2-7.

Table 2-9. Additional portable WAR files required for JWSDP deployment

Name

Description

WEB-INF/web.xml

Web application deployment descriptor

WEB-INF/jaxrpc-ri.xml

JWSDP-specific deployment information

WEB-INF/model

Model file generated by wscompile

A web.xml file typically contains information that describes the servlets that make up a web application, together with the URL mappings for those servlets. It may also contain initialization parameters and security constraints that must be applied to protect some or all of the web application's resources. The web.xml file for the book web service example, which can be found in the directory chapter2\bookservice\deploy , is shown in Example 2-13.

Example 2-13. The web.xml file for the book web service for deployment with the JWSDP
 <?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">

    
<web-app>
    <display-name>JAX-RPC Book Service</display-name>
    <description>Book Service Web Application using JAX-RPC</description>
</web-app> 

As you can see, this file contains only a description of the web application. Information relating to the deployment of the service, such as the servlet to use and the URL mapping, is not included. Instead, the JAX-RPC reference implementation in the JWSDP generates this information based on the content of the jaxrpc-ri.xml file when it creates the deployable WAR file.

In most cases, it would not be strictly necessary to include a model file in the portable archive. If you choose not to do so, then during the process of creating a deployable WAR file, a WSDL definition for the service is created based on the Java interface that defines the service endpoint, whereas if the model file is included in the archive and referenced from the jaxrpc-ri.xml file (as shown next), the WSDL definition is built from the information in the model file. The difference between these two approaches is as follows:

  • If the model file is used, the service name in the WSDL file is as specified to wscompile , which in this case is BookService . If it is not specified, the service name is the name of the Java interface file ”in this case, BookQuery . In many cases, this is not significant, because it is not visible to a client that uses statically generated stubs. It may be considered relevant if, as will be shown in Chapter 6, the client stubs are created from the WSDL definition rather than from the Java interface definition, since the name of the service class will not match that used in the static client.

  • In some cases, it is essential to include the model file because the Java interface definition alone does not convey everything about the way in which the service should behave. Some examples of this will be given in Chapter 6.

All of the JAX-RPC examples in this book include the model file in the deployable web archive.

2.2.7.2 Creating a deployable WAR file

To create a deployable WAR file for deployment in the JWSDP, use a command-line utility called wsdeploy . The basic form of this command requires only the names of the portable WAR file and deployable WAR file that is to be created:

 wsdeploy -o targetFileName portableWarFileName 

You'll find a more complete description of wsdeploy in Chapter 8. To create the deployable WAR file for the book web service, you can use the web-package target of the Ant buildfile:

 ant web-package 

This command writes its output to the file chapter2\bookservice\Books.war . Note that, despite its name, the wsdeploy command simply creates the deployable WAR file and does not actually deploy it to an application server.

The process of creating the deployable archive is driven mainly by the content of the jaxrpc-ri.xml file, which is described later. The archive will contain the following:

  • The class files and resources that were supplied in the portable WAR file.

  • A compiled class file for the implementation-dependent tie required by the service endpoint interface. In the case of the BookQuery interface, this class is called BookQuery_Tie .

  • Compiled class files for the serializers required to encode the method calls and data types used by the service into SOAP messages and to decode them. These files are the same as those created by wscompile when generating the client-side stubs. This makes sense, because each serializer contains the code to both encode and decode a data type or an RPC message; therefore, the same classes can be used by both the client and the server.

  • A WSDL file that describes the web service in a form that can be used by other developers to create clients that can use the service. This file is placed in the WEB-INF directory of the archive and is created either from the Java interface representing the service endpoint or from the model file, if it is included.

  • A copy of the JAX-RPC model file for the service, placed in the WEB-INF directory. This is either a copy of the model file that is included in the portable archive or, if it is not included, a compressed binary file built by inspecting the Java service interface definition. For the book web service, if this file is generated by wsdeploy , it would be called BookQuery_model.xml.gz . For more information on the use of a model file, refer to the description of the wscompile command in Chapter 8.

  • A modified version of the web.xml file.

  • A file called jaxrpc-ri-runtime.xml , which is based on the content of the jaxrpc-ri.xml file supplied in the portable WAR file.

When the web service is deployed as a web application, SOAP messages addressed to the URLs that correspond to the application will be delivered to a servlet (as shown in Figure 2-10) based on a servlet-mapping element in the web.xml file. This element, together with the name of servlet itself, was not supplied in the original web.xml file shown in Example 2-13, but is added by wsdeploy . Example 2-14 shows the version of web.xml that appears in the deployable archive.

Example 2-14. The modified web.xml file included in the Books.war file
 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
          "http://java.sun.com/j2ee/dtds/web-app_2_3.dtd">
<web-app>
  <display-name>JAX-RPC Book Service</display-name>
  <description>Book Service Web Application using JAX-RPC</description>
  <listener>
    <listener-class>com.sun.xml.rpc.server.http.JAXRPCContextListener
    </listener-class>
  </listener>

  <servlet>
    <servlet-name>BookQuery</servlet-name>
    <display-name>BookQuery</display-name>
    <description>JAX-RPC endpoint - BookQuery</description>
    <servlet-class>com.sun.xml.rpc.server.http.JAXRPCServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>BookQuery</servlet-name>
    <url-pattern>/BookQuery</url-pattern>
  </servlet-mapping>
</web-app> 

The servlet element causes a servlet called JAXRPCServlet (which is part of the JWSDP reference implementation) to be loaded when the web application is started, while the servlet-mapping element causes all URLs that have the string BookQuery immediately following the context part, such as http://localhost:8080/Books/BookQuery , to be delivered to it. The web.xml file does not, of course, contain the information that the servlet will need to locate the correct tie and servant classes for the web service, which will be needed in order to handle SOAP messages. This information is supplied in the jaxrpc-ri.xml file, which the developer creates and includes in the portable WAR file. The content of this file, as used to deploy the book web service, is shown in Example 2-15.

Example 2-15. The jaxrpc-ri.xml file for the book web service
 <?xml version="1.0" encoding="UTF-8"?>
<webServices
   xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd"
   version="1.0"
   targetNamespaceBase="urn:jwsnut.chapter2.bookservice/wsdl/"
   typeNamespaceBase="urn:jwsnut.chapter2.bookservice/types/">

    <endpoint
       name="BookQuery"
       displayName="BookQuery Port"
       description="Book Query Port" 
       model="/WEB-INF/model" 
       interface="ora.jwsnut.chapter2.bookservice.BookQuery"  
       implementation="ora.jwsnut.chapter2.bookservice.BookServiceServant"/> 

     <endpointMapping
        endpointName="BookQuery"
        urlPattern="/BookQuery"/>
</webServices> 

The webServices element in this file may contain any number of endpoint elements, followed by any number of endpointMapping elements. [18] Each endpoint element describes a single service endpoint interface that will be managed by the servlet. The name attribute provides a symbolic name that links it to an endpointMapping element that has an endpointName attribute with the same value. When such an endpointMapping element exists, its urlPattern attribute is used to create the url-pattern element of the servlet-mapping for the service endpoint in the web.xml file, which in this case is /BookQuery . If more than one endpoint is included in the webServices element, then there will be a servlet-mapping in the web.xml file for each of them. This means that more than one web service can be managed by a single servlet.

[18] The targetNamespaceBase and typeNamespaceBase attributes of the webServices element are not described here, because they are not relevent to this discussion. They are, however, covered in Section 2.2.7.5, later in this chapter.

It is not necessary to include an endpointMapping element for each endpoint element. If you choose not to provide such an element, however, you must add an additional attribute to the webServices element that will be used to create the URL pattern for that endpoint. Example 2-16 shows an alternative to the jaxrpc-ri.xml file shown in Example 2-15 that does not use an endpointMapping element.

Example 2-16. A jaxrrpc-ri.xml file with no endpointMapping element
 <?xml version="1.0" encoding="UTF-8"?>
<webServices
    xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd"
    version="1.0"
    targetNamespaceBase="urn:jwsnut.chapter2.bookservice/wsdl/"
    typeNamespaceBase="urn:jwsnut.chapter2.bookservice/types/"  urlPatternBase="/base  ">

    <endpoint
        name="BookQuery"
        displayName="BookQuery Port"
        description="Book Query Port" 
        model="/WEB-INF/model"
        interface="ora.jwsnut.chapter2.bookservice.BookQuery"  
        implementation="ora.jwsnut.chapter2.bookservice.BookServiceServant"/> 
</webServices> 

Since there is no endpointMapping element corresponding to the BookQuery endpoint element, wsdeploy uses the urlPatternBase attribute of the webServices element to create a default mapping using the pattern:

 urlPatternBase + '/' + endpoint name 

In this case, this produces the result:

 /base/BookQuery 

Using this setup, assuming that the web application is deployed with the context path Books , a client program needs to use the URL http://localhost:8080/Books/base/BookQuery to access the book web service, instead of http://localhost:8080/Books/BookQuery . As you can see from Example 2-15, you don't need to supply a value for urlPatternBase if every

 endpoint 

element has a matching endpointMapping .

Two other attributes in the endpoint element supply the other information that is required when decoding a URL from a client request. The interface attribute names the Java service endpoint interface that corresponds to the endpoint, while the implementation attribute provides the class name of the servant class. When wsdeploy processes this file, it creates a modified version of the file (called jaxrpc-ri-runtime.xml ) that includes the name of the tie class that it generates for this endpoint (which is the information that the servlet really needs, together with the servant class name). Example 2-17 shows the jaxrpc-ri-runtime.xml file generated for the book web service, with the interesting attributes of the endpoint element highlighted in bold. Since this file is private to the JAX-RPC reference implementation, its format may change at any time. Fortunately, you will never have to create one of these files by hand.

Example 2-17. The jaxrpc-ri-runtime.xml file for the book web service
 <?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns='http://java.sun.com/xml/ns/jax-rpc/ri/runtime' version='1.0'>
  <endpoint
    name='BookQuery'  interface='ora.jwsnut.chapter2.bookservice.BookQuery'
    implementation='ora.jwsnut.chapter2.bookservice.BookServiceServant'
    tie='ora.jwsnut.chapter2.bookservice.BookQuery_Tie'  model='/WEB-INF/BookQuery_model.xml.gz'
    wsdl='/WEB-INF/BookQuery.wsdl'
    service='{urn:jwsnut.chapter2.bookservice/wsdl/BookQuery}BookQuery'
    port='{urn:jwsnut.chapter2.bookservice/wsdl/BookQuery}BookQueryPort'
    urlpattern='/BookQuery'/>
</endpoints> 

The jaxrpc-ri-runtime.xml file is placed in the WEB-INF directory of the deployable archive, where it can be located and read by the servlet during initialization.

2.2.7.3 Deploying the service implementation

Once you have the deployable WAR file, there are several ways in which you can deploy it. The context path associated with the web application that is created depends on the deployment method, as described in the following paragraphs.

Manually

If you are using the JWSDP with the Tomcat web container, a manual deployment can be performed by stopping the web server, copying the Books.war file into its webapps directory, and then restarting the web server. This is all that is required to complete the deployment. Although this is very simple, it is inconvenient because it involves manually stopping and starting the server each time you make a change to the service. The context path for the web application in this case is taken from the name of the web archive file with the .war suffix removed, and will therefore be Books in this case.

Using the Ant buildfile

The Tomcat web server includes a built-in web application called manager that can be used to programmatically deploy another web application. The JWSDP includes Ant tasks that use the manager application to automate the tasks of deployment and undeployment. The Ant buildfile for the example source code provides targets called deploy ,

 undeploy 

, and redeploy , that take advantage of these tasks, the use of which is described in Section 2.1.2.5, earlier in this chapter. In this case, the context path for the web application is explicitly provided as a parameter to the Ant task.

2.2.7.4 Special handling for the HTTP GET request

Although SOAP messages from JAX-RPC clients are sent using HTTP POST requests, it is worth noting that JAXRPCServlet provides some useful behavior when it receives an HTTP GET request for the URL corresponding to a service endpoint. The information returned in response to these requests is intended for display in a browser and can be used to check that the service is properly deployed, or by developers that need to get a copy of the WSDL document that describes your service.

If, for example, you point your browser at the URL for the book web service, you get the result shown in Figure 2-11. You'll notice that the web page shown in the figure contains two further URLs that are directly derived from the URL of the service itself:

Figure 2-11. Web page returned by a GET request on the book service URL
figs/jwsn_0211.gif
http://localhost:8080/Books/BookQuery?WSDL

Returns the WSDL document for the service. When you define a web service in terms of Java interfaces, the WSDL is created and deployed for you by wsdeploy . In Chapter 6, you'll see how to create the client stubs needed to allow a JAX-RPC client to access an existing web service given a WSDL document that describes it. The result of using this URL was shown in Figure 2-9 earlier in this chapter.

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

Fetches a compressed binary file that contains an internal representation of the web service as created by wsdeploy . As noted in Section 2.2.2, earlier in this chapter, you can supply the content of this file to wscompile as another way to create the JAX-RPC client-side stubs for an existing web service.

2.2.7.5 JWSDP configuration files and XML namespaces

If you compare the content of the config.xml file used to generate the client-side stubs in Example 2-9 with the jaxrpc-ri.xml file for the server-side deployment shown in Example 2-15, you'll notice that they both have elements that contain namespace-related attributes. In the case of the config.xm l file, these attributes are associated with the service element:

 <service name="BookService"  targetNamespace="urn:jwsnut.chapter2.bookservice/wsdl/BookQuery  "  typeNamespace="urn:jwsnut.chapter2.bookservice/types/BookQuery  "
   packageName="ora.jwsnut.chapter2.bookservice"> 

while in jaxrpc-ri.xml they appear with the webServices element:

 <webServices
    xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/dd"
    version="1.0"  targetNamespaceBase="urn:jwsnut.chapter2.bookservice/wsdl/  "  typeNamespaceBase="urn:jwsnut.chapter2.bookservice/types/">  

In Chapter 6, you'll see exactly how the namespaces specified by these attributes are used when building the SOAP messages that are exchanged by the client and server. For now, it is important to ensure that they specify consistent values ” otherwise the server will not be able to decode messages sent to it by the client.

The key to using the correct value is to start by choosing the values of targetNamespaceBase and typeNamespaceBase in the jaxrpc-ri.xml file, which fixes their values for the server-side implementation. A namespace can be any valid URI; here, we chose to use a URN that clearly indicates that the service is part of the example source code for this chapter. By convention, the suffixes wsdl and types are added to indicate the different uses that are made of these namespaces.

As their names imply, these attributes are just base values. When the ties and the WSDL document for each service endpoint interface in the jaxrpc-ri.xml file are generated, wsdeploy appends the name of the endpoint to each base name to create the namespace for that endpoint. For the BookQuery endpoint, therefore (see the endpoint element in Example 2-15), the namespaces are:

Types namespace

urn:jwsnut.chapter2.bookservice/wsdl/BookQuery

Target namespace

urn:jwsnut.chapter2.bookservice/types/BookQuery

Note that it is necessary to add the "/" to the targetNamespaceBase and typeNamespaceBase attributes in the jaxrpc-ri.xml file in order for these names to be formed correctly.

These two namespace URIs are the ones that should be used for the targetNamespace and typeNamespace attributes in the config.xml file. Another way to locate the correct namespace is to look at the WSDL file that wsdeploy places in the deployable WAR file. In the case of book web service, this file is called BookService.wsdl. [19] At the start of this file, you will find the following elements:

[19] This name is used only if you include a model file in the deployable web archive and reference it from jaxrpc-ri.xml . If you do not, then the WSDL file will be called BookQuery.xml and the name attribute in the definitions element will be BookQuery .

 <?xml version="1.0" encoding="UTF-8"?>

<definitions name="BookService"  targetNamespace="urn:jwsnut.chapter2.bookservice/wsdl/BookQuery  "
    xmlns:tns="urn:jwsnut.chapter2.bookservice/wsdl/BookQuery" 
    xmlns="http://schemas.xmlsoap.org/wsdl/" 
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
    xmlns:ns2="urn:jwsnut.chapter2.bookservice/types/BookQuery" 
    xmlns:ns3="http://java.sun.com/jax-rpc-ri/internal">
  <types>  <schema targetNamespace=
        "urn:jwsnut.chapter2.bookservice/types/BookQuery  "
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
           xmlns:tns="urn:jwsnut.chapter2.bookservice/types/BookQuery" 
           xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/" 
           xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" 
          xmlns="http://www.w3.org/2001/XMLSchema"> 

The correct values to use for the typeNamespace and targetNamespace attributes in the config.xml file are given by the targetNamespace attributes of the schema and definitions elements, respectively. This technique can also be used when you need to create a client to communicate with an existing service for which you only have a WSDL definition. You'll find more about WSDL in Chapter 5.