6.2 ServiceFactory and the Service Interface

   

6.2 ServiceFactory and the Service Interface

In Chapter 2, you saw that in order to get a stub for a web service endpoint interface, it was first necessary to obtain a reference to a Service object, using code like this:

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

BookInfo[] books = bookQuery.getBookInfo(  ); 

where BookService_Impl is a class generated by wscompile that implements the javax.xml.rpc.Service interface. As noted in Chapter 2, there is no standard naming convention for this generated class (although the JAX-RPC specification recommends one); therefore, making use of the class name in this way introduces a dependency in your code on a specific JAX-RPC implementation. Although there is nothing that you can do about this if your client application uses the stubs generated by wscompile , it is possible to make your code more portable if you use dynamic proxies or the dynamic invocation interface instead of static stubs, or if you build a J2EE application client instead of a standalone J2SE client. In order to see how this can be done, it is necessary first to look in more detail at the Service interface and the ServiceFactory class to which it is related .

6.2.1 The ServiceFactory Class

ServiceFactory is an abstract class that can be used to create Service objects in a portable manner. The public methods of ServiceFactory are shown in Example 6-2.

Example 6-2. The ServiceFactory class
 public abstract class ServiceFactory {
  public static ServiceFactory newInstance(  );
  public abstract Service createService(URL wsdlLocation, QName serviceName);
  public abstract Service createService(QName serviceName);
} 

The static newInstance( ) method returns an implementation-dependent instance of the ServiceFactory class:

 ServiceFactory factory = ServiceFactory.newInstance(  ); 

Although it is unlikely that you would ever need to do so, you can change the ServiceFactory implementation class by setting the system property ServiceFactory.SERVICEFACTORY_PROPERTY to the name of the class to be used. [1]

[1] Properties such as this are used not by developers, but by third-party vendors that provide their own implementations of JAX-RPC. By setting an appropriate value, they cause their own classes to be instantiated instead of the default classes provided by the reference implementation.

The two createService( ) methods return instances of an implementation-dependent class that implements the Service interface. The first variant returns a Service object that has access to the ports of a service described in a WSDL document whose URL is supplied as its first argument. The name of the required service is supplied in the form of a QName object. QName represents an XML namespace-qualified name and is therefore composed of a namespace part and a local part. Here is how to create a QName object that refers to the book service developed in Chapter 2:

 QName serviceName = new QName("urn:jwsnut.chapter2.bookservice/wsdl/
BookQuery",  "BookService"); 

The constructor requires the namespace and local parts of the name, in that order. The namespace part is determined by the targetNamespace attribute of the definitions element in the WSDL definition of the service, while the local name comes from the name attribute of the service element. Refer to Appendix A for a complete listing of the WSDL document for this service. The following code obtains the actual Service object for this service:

 URL wsdlURL = new URL("http://localhost:8000/Books/BookQuery?WSDL");
Service service = factory.createService(wsdlURL, serviceName); 

The second variant of createService( ) requires only a QName argument. A Service object returned by this method can only be used in conjunction with the dynamic invocation interface, which is described later in this chapter.

ServiceFactory is intended to be used by standalone J2SE clients in the case where the target service is defined in a WSDL document. However, for container-resident clients (such as J2EE application clients ), you should use the technique covered in Section 6.4 later in this chapter to get a reference to a Service object instead of using ServiceFactory .

6.2.2 The Service Interface

An object that implements the Service interface provides the client-side view of a web service (despite the name that might lead you to think that this is a server-side interface). Logically, it maps to the entity represented by the service element in a WSDL document. The methods that make up the Service interface are shown in Example 6-3.

Example 6-3. The javax.xml.rpc.Service interface
 public interface Service {
  // Accessors
  public HandlerRegistry getHandlerRegistry(  );
  public TypeMappingRegistry getTypeMappingRegistry(  );
  public QName getServiceName(  )
  public URL getWSDLDocumentLocation(  );

  // Access to ports
  public Iterator getPorts(  ) throws ServiceException;
  public Remote getPort(Class endpointInterface) throws ServiceException;
  public Remote getPort(QName portName, Class endpointInterface) 
      throws ServiceException;

  // DII-related methods
  public Call createCall(  ) throws ServiceException;
  public Call createCall(QName portName) throws ServiceException;
  public Call createCall(QName portName, QName operationName) 
      throws ServiceException;
  public Call createCall(QName portName, String operationName)
      throws ServiceException;
  public Call[] getCalls(QName portName);
} 

When you use wscompile to create stubs from a Java interface definition or from a WSDL file, one of the files that is generated is a class that implements the Service interface. The generated Service class includes a method that can be used to get a client-side stub for the service endpoint interface. In the book service example used in Chapter 2, for example, the following method is generated:

 public BookQuery getBookQueryPort(  ) 

This method is not, of course, part of the Service interface and it is obviously not, therefore, available from a Service object returned by the ServiceFactory createCall( ) method. If you use a ServiceFactory to create your Service object instead of directly instantiating a generated Service class, you will need to use one of the getPort( ) methods listed in Example 6-3 to get access to an object that can be used to invoke the service endpoint's methods. These methods may, according to the JAX-RPC specification, return an instance of a precompiled stub, just as the getBookQueryPort( ) method would. It may also return an object called a dynamic proxy that is created on-the-fly . The use of the getPort( ) methods is discussed later.

The various createCall( ) methods return a Call object that can be used to invoke an operation that belongs to the web service's endpoint interface without requiring the generation and compilation of a stub. The Call object is the core class for the JAX-RPC dynamic invocation interface (DII), which provides lower-level access than either generated stubs or dynamic proxies, at the expense of additional coding. Clients that use the DII can make one-way calls, whereas those that use the other mechanisms are restricted to request-response operations. DII is covered in Section 6.3, later in this chapter.

The remaining four Service methods can be used to access various items of state:

public String getServiceName( )

Returns the name of the service.

public URL getWSDLDocumentLocation( )

Gets the URL for the WSDL document for the service. This is null if the WSDL document location is not known ” that is, if the Service object was generated by wscompile starting from a Java interface definition, or if it was returned by the variant of the ServiceFactory createService( ) method that does not require a WSDL document URL as one of its arguments.

public HandlerRegistry getHandlerRegistry( )

Returns a reference to the configured SOAP handlers for this service. A client can install handlers to perform processing on a SOAP message created as a result of a JAX-RPC call before it is transmitted, or on a response message when it is received and before its content is used to generate the results of the JAX-RPC call. The HandlerRegistry is described in Section 6.8, later in this chapter.

public TypeMappingRegistry getTypeMappingRegistry( )

Returns a reference to the TypeMappingRegistry for this service. This registry contains the serializers and deserializers that are used to convert between Java primitive types and objects and their XML representation in SOAP messages. See Section 6.9, later in this chapter, for a discussion of this registry and how its contents are determined.

6.2.3 Using the getPort( ) Methods of a Generated Service Object

Although a Service object (such as BookService_Impl ) generated by wscompile has a method (like getBookQueryPort( ) ) that can be used to get a client-side stub, it is still possible to call the getPort( ) method of such an object instead. This method has two variants, the first of which requires a QName object that identifies the port and a reference to a class object for the service endpoint interface:

 public Remote getPort(QName portName, Class endpointInterface); 

The port name is formed by combining a namespace with the local name of the port. The way in which you obtain these names depends on whether wscompile generated the Service object from Java interface definitions (as was the case in Chapter 2) or from a WSDL file:

  • For a Service generated from a Java interface definition, the namespace value is taken from the targetNamespace attribute of the configuration element in the config.xml file, while the local name is the name of the Java interface itself with the string Port appended to it. Therefore, in Example 2-9, you can see that the namespace value for the BookQuery port of the book web service should be urn:jwsnut.chapter2.bookservice/wsdl/BookQuery , while the local part of the name is BookQueryPort , since BookQuery is the name of the interface defined in Example 2-3.

  • For a Service created from WSDL, the namespace is the one that applies to the port as defined in the WSDL document, which is determined by the targetNamespace attribute of the definitions element enclosing the port element. The local part of the name is taken from the name attribute of the port element itself. In the case of the book web service, the namespace is therefore urn:jwsnut.chapter2.bookservice/wsdl/BookQuery (see Example 5-2) and the local name is BookQueryPort (see Example 5-8).

In both cases, then, an appropriate QName object for the service endpoint interface of the book web service can be created as follows :

 QName portName = new QName("urn:jwsnut.chapter2.bookservice/wsdl/BookQuery", 
                           "BookQueryPort"); 

The following code sequence can therefore be used to get an object that can be used to invoke the methods of this interface:

 BookService_Impl service = new BookService_Impl(  );
QName portName = new QName("urn:jwsnut.chapter2.bookservice/wsdl/BookQuery", 
                           "BookQueryPort");  BookQuery bookQuery =  (BookQuery)service.getPort(portName  ,  BookQuery.class);  

The second variant of getPort( ) requires only the Class object for the Java interface:

 BookService_Impl service = new BookService_Impl(  );  BookQuery bookQuery = (BookQuery)service.getPort(BookQuery.class);  

Because the getPort( ) method is defined to return only a class that implements the Remote interface, it is necessary to explicitly cast the returned value to the actual interface type before it can be used to invoke the methods of the web service's endpoint interface.

When used with a generated Service object, these two variants of the getPort( ) method are equivalent. Therefore, there is no advantage to using the variant that requires a QName to describe the port in addition to the service interface Class object. In fact, in the reference implementation, both of these methods return the same generated stub object that would be returned by the getBookQueryPort( ) method, so there is little reason to use getPort( ) when you are dealing with a generated Service object.

6.2.4 Using getPort( ) with a Service Object from a ServiceFactory

When you obtain your Service object from a ServiceFactory , you can't use a method like getBookQueryPort( ) to get a client-side stub. Therefore, you have to use the getPort( ) methods (or use DII, which is considerably more complex). Consider the code shown in Example 6-4.

Example 6-4. Using the getPort( ) method with a Service object created by a ServiceFactory
 ServiceFactory factory = ServiceFactory.newInstance(  );
QName serviceName = new QName("urn:jwsnut.chapter2.bookservice/wsdl/BookQuery",
                              "BookService");
URL wsdlURL = new URL("http://localhost:8000/Books/BookQuery?WSDL");
Service service = factory.createService(wsdlURL, serviceName);

QName portName = new QName("urn:jwsnut.chapter2.bookservice/wsdl/BookQuery",
                           "BookQueryPort");
BookQuery bookQuery = (BookQuery)service.getPort(portName, BookQuery.class) 

This code uses a ServiceFactory to obtain a Service object for a web service defined in a WSDL document and then calls its getPort( ) method to get an object that refers to the service's endpoint interface. As you can see, there is no dependency on any client-side artifacts of the type created by wscompile (and in particular, no reliance on knowledge of the class names used for such artifacts). The only things that this code uses that might differ from service to service are the location of the WSDL document and the name of the service within it for which a Service object is required. The fact that you can access a web service in this way without having to pregenerate either a Service object or the client-side stubs seems to greatly simplify the process of building web service clients. Moreover, it appears that this code is completely independent of any particular client-side JAX-RPC implementation, whereas applications that use generated Service objects are not, since they need to use the name of the Service class, which is not defined by the JAX-RPC specification. However, there are a few points to bear in mind when choosing this approach:

  • The ServiceFactory createService( ) method needs to read and parse the WSDL document at runtime in order to determine the services and ports that it defines. Apart from the parsing overhead that this incurs, there may be a noticeable delay if the WSDL document resides on a host that is accessed over the Internet. By contrast, when you use wscompile to pregenerate the Service object and the client-side stubs, this overhead is incurred only while the client application is being built.

  • Even though the WSDL document is read at runtime, you still need to have prefetched it in order to determine the fully qualified name of the service, which the createService( ) method requires.

  • Although using the ServiceFactory class allows you to avoid using precompiled client-side stubs, you still have to create a Java interface for the service endpoint itself. In this case, this means generating and compiling the class file for the BookQuery interface. The easiest way to do this is, of course, to use wscompile .

  • When you use wscompile to generate client-side stubs, the getBookQueryPort( ) and getPort( ) methods simply have to create and return an instance of the stub class. However, when you use the getPort( ) method of a Service object returned from a ServiceFactory , there is no stub class available. Instead, getPort( ) constructs and returns an object, called a dynamic proxy , [2] that implements the methods of the service endpoint interface ( BookQuery ), as well as implementing the javax.xml.rpc.Stub interface so that it functions properly as a client-side stub. The process of creating this object involves runtime overhead that will probably be greater than simply instantiating an existing class.

    [2] Dynamic proxies were introduced in J2SE Version 1.3. You don't need to understand how dynamic proxies work in order to use the one returned by the getPort( ) method, but if you are curious , refer to the descriptions of java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler in the J2SE documentation bundle or in Java in a Nutshell , by David Flanagan (O'Reilly).

Once you have a Service object obtained from a ServiceFactory , you can use either of its two getPort( ) methods to get a reference to a service endpoint interface. It is probably better to use the variant that supplies both the port name and the service endpoint interface class, as shown in Example 6-4. If you use the variant that supplies only the service endpoint interface class, the required endpoint is located by searching the WSDL document for a portType that has operation elements that map to the methods defined in the service endpoint interface class. This is potentially a very time-consuming process and therefore should be avoided wherever possible.

You can try out an example that uses dynamic proxies by opening a command window, making your working directory chapter6\proxybookservice relative to the installation directory of the example source code for this book, and then typing the commands:

 ant generate-client
ant compile-client
ant run-client 

As with the previous example in this chapter, this provides a client for the book web service developed in Chapter 2 by importing its WSDL definition. Therefore, you must have already started the web container and deployed the book web service before typing these commands, so that the WSDL definition can be obtained from the web container.

The generate-client target uses wscompile to access the WSDL file and generate the definitions of the BookQuery interface and the BookInfo object that it relies on, using the -import option of wscompile (see Section 6.4, later in this chapter, or Chapter 8 for a description of this option). The same config.xml file is shown in Section 6.1 earlier in this chapter. The run-client target uses a client application to invoke the getBookCount( ) method of the book web service and then prints the result. The output from this command should look like this:

 run-client:
     [java] Book count = 12 

If you see the error message "Missing port information" instead of the output shown above and you are running the beta version of J2EE 1.4, you need to apply a workaround to a bug that affects clients generated from WSDL documents in that release. Refer to the note in Section 6.1, earlier in this chapter, for details of the problem and the workaround.

The important point to note about this example is that, as the following code extract shows, once the BookQuery object is obtained, the fact that it happens to be a dynamic proxy is not of any concern to the application, which invokes its methods in the same way as it would invoke those of a generated stub:

 ServiceFactory factory = ServiceFactory.newInstance(  );
QName serviceName = new QName(
     "urn:jwsnut.chapter2.bookservice/wsdl/BookQuery",  "BookService"); 
Service service = factory.createService(wsdlURL, serviceName);
QName portName = new QName("urn:jwsnut.chapter2.bookservice/wsdl/BookQuery",
                           "BookQueryPort");  BookQuery bookQuery =  (BookQuery)service.getPort(portName  ,  BookQuery.class);   int bookCount = bookQuery.getBookCount(  );  

Notice also that, because the BookQuery object was created from information in a WSDL document, there is no need to cast it to the type javax.xml.rpc.Stub and use the _setProperty( ) method to set the endpoint address. You could, however, perform this cast to set any of the stub properties listed in Table 2-6 if this is required, since the dynamic proxy returned by getPort( ) implements the Stub interface as well as BookQuery .

The full command line for this example application provides options that allow you to use the other methods of the service endpoint interface. The general form of the command line is as follows:

 ProxyBookServiceClient wsdlURL [args] 

Here, wsdlURL is the URL to be used to obtain the WSDL document for the service, and args is the additional argument that indicates which method is to be invoked and supplies any required parameters. The run-client target does not supply any additional arguments, the result of which is the getBookCount( ) method being called. Other legal argument combinations are as follows:

Arguments

Description

author title

Gets the name of the author of the book with the given title, using the getAuthor( ) method

editor title

Gets the name of the book's editor, using the getEditor( ) method

price title

Gets the book's price, using the getTitle( ) method

list

Gets a list of all books, using the getBookInfo( ) method

You can use the CLIENT_ARGS property to set the arguments to be passed to the application. For example, the following command line calls the getEditor( ) method to get the name of the editor of the book Java Swing :

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

The result is:

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