Main Page

Previous Section Next Section

JAX-RPC Development

We have just covered how data can be transferred over the wire, along with the rules and associated mechanics governing that. In this section, we will look at how services can be developed and realized using JAX-RPC and the steps involved in doing so.

Developing and consuming a JAX-RPC service can be categorized into five steps:

  1. Service definition

  2. Service implementation

  3. Service deployment

  4. Service description

  5. Service consumption

In walking through these steps we will develop the service example introduced in Chapter 5. The example illustrates a bill payment service developed by Flute Bank as part of its online operations.

1. Service Definition

The term service definition is used to refer to the abstraction that defines the publicly surfaced view of the service. The service definition is represented as a Java interface that exposes the service's operations. The service definition is also called a remote interface, because it must extend the java.rmi.Remote interface, and because all methods in it must throw a java.rmi.RemoteException. The code below shows the BillPay Web service:

package com.flutebank.billpayservice;

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

public interface BillPay extends Remote {
       public PaymentConfirmation schedulePayment(Date date, String nickName, double
                             amount) throws ScheduleFailedException, RemoteException;
       public PaymentDetail[] listScheduledPayments() throws RemoteException;
       public double getLastPayment(String nickname) throws RemoteException;
}

The methods in the interface must have valid JAX-RPC data types (disussed earlier) as arguments and return types. If they are not a supported data type (e.g. java.util.Map), then appropriate serializers and deserializers must be available, so that these types can be marshaled and unmarshalled to and from their corresponding XML representations. The data type can also be a holder class. Holders and pluggable serializers are covered later in this chapter.

An implemenatation will usually verify this type information at compile time and warn the developer if it is not correct. A request sent with incorrect type information at runtime will generate a SOAP fault, because it will not be able to unmarshall the XML.

2. Service Implementation

The service implementation, also known as a servant, is the concrete representation of the abstract service definition; it is a class that provides the implementation or the service definition. The Java class must have a default constructor and must implement the remote interface that defines the service. Listing 10.2 shows the implementation for the BillPay service.

Listing 10.2: Implementation for Flute Bank's BillPay service
Start example
package com.flutebank.billpayservice;

import java.util.Date;

public class BillPayImpl implements BillPay {

public BillPayImpl(){}

public PaymentConfirmation schedulePayment(Date date, String nickName, double
                                              amount) throws ScheduleFailedException {
       // invoke business logic like EJBs here
              return new PaymentConfirmation(81263767,"Sprint PCS", amount);
           }
public PaymentDetail[] listScheduledPayments() {
       // lookup the detail objects and other business logic from EJBs here
       PaymentDetail details[]=new PaymentDetail[1];
       PaymentDetail dummy= new PaymentDetail("Digital Credit
                                                    Union","Credit",2000, new Date());
       details[0]=dummy;
       return details;
    }

public double getLastPayment(String nickname) {
    // lookup the detail objects and other business logic from EJBs for this
    // nickname based on the callers user id
              if(nickname.equalsIgnoreCase("my cable tv provider"))
                     return 829;
              else
                     return 272;
    }
}
End example

Services are deployed in a JAX-RPC runtime, which is a container that implements the JAX-RPC specifications. By default, the runtime will just invoke the methods corresponding to the RPC request in the Java implemenatation. The service implementation can choose to provide hooks to allow the runtime to manage the service's lifecycle and allow the container to invoke callbacks on the service when major lifecycle events occur. The "hook" is defined as a javax.xml.rpc.server.ServiceLifeCycle interface that the service can implement. The container will then invoke methods on this service appropriately, via this interface. The interface defines an init(Object context) and a destroy() method:

public interface ServiceLifecycle{
       public void init(Object obj) throws ServiceException;
       public void destroy();
}

The behavior of these methods is similar to the init() and destroy() methods in a servlet. When the implementation is first instantiated, the init() method is invoked, and a context object passed to it, the destroy() method is called before the implementation needs to be removed (e.g., at shutdown or during a resource crunch). These methods are good places to initialize and release expensive resources, such as database connections and remote references. The context is defined as an Object, to allow for different endpoint types to be used, as we will see later (e.g., the context will be different for an HTTP endpoint and a JMS endpoint).

As with a servlet, an implementation should not hold a client-specific state in instance variables, because the runtime can invoke methods from multiple threads. Architects should also avoid synchronizing the methods themselves. There are other ways to maintain client state, as discussed in the next section.

3. Service Deployment

We mentioned earlier that a service is deployed in a JAX-RPC runtime. A service endpoint is the perimeter where the SOAP message is received and the response dispatched. It is the physical entity exposed to service consumers that essentially services client requests. An endpoint is provided by the runtime and is not written by developers. An endpoint is bound to the transport protocol. Because a runtime is required to support an HTTP transport, JAX-RPC also defines the behavior of an endpoint for this protocol as a Java servlet, as Figure 10.4 shows.

Click To expand
Figure 10.4: Service deployment

The servlet receives the SOAP message as the HTTP request, determines the servant to use for servicing that request, and delegates to it or its proxy representation (the tie). Once the service has done its work, the servlet is responsible for packaging the SOAP message and sending it back over HTTP.

The exact implementation of the servlet endpoint is left up to the runtime. The reference implementation contains a single servlet (com.sun.xml.rpc.server .http.JAXRPCServlet) that delegates to a tie, based on the xrpcc-generated properties file (we will see this later in the chapter). Because the endpoint is a servlet, it requires a Servlet 2.2-compliant container. Also, the packaging and deployment to the endpoint of the service has to be the standard J2EE WAR file, with its defined structure (WEB-INF/classes and the web.xml file, etc.)

If a service implementation implements the ServiceLifeCycle interface, the context object passed in the init() is of type javax.xml.rpc.server.ServletEndpointContext:

public interface ServletEndpointContext{
       public MessageContext getMessageContext();
       public Principal getUserPrincipal();
       public HttpSession getHttpSession();
       public ServletContext getServletContext();
}

This context provides methods to access the MessageContext, Principal, HttpSession and ServletContext objects associated with the user. The listing below shows an example of how this can be used. These objects are good places for maintaining different kinds of state information:

  • The HttpSession is a good place to maintain client-specific state, using the getAttribute() and setAttribute() methods.

  • The ServletContext is a good place to access application-specific state, such as configuration parameters, Java Naming and Directory Interface (JNDI) names, and JNDI contexts, using the getAttribute() and setAttribute() methods.

  • The MessageContext is a good place to obtain state set by message handlers during preprocessing of the message. Handlers are covered in detail later in the chapter.

public class BillPayImpl implements BillPay, ServiceLifecycle {
     private ServletEndpointContext ctx;
public void init(java.lang.Object context){
       ctx=(ServletEndpointContext)context;
    }
public PaymentDetail[] listScheduledPayments() {
       SOAPMessageContext msgctx= (SOAPMessageContext) (ctx.getMessageContext());
       HttpSession session = ctx.getHttpSession();
          ServletContext servletctx= ctx.getServletContext()
      // other code
      }
}

The usage of the ServletEndpointContext is analogous to the SessionContext and EntityContext in EJBs.

4. Service Description

Once the service is defined, implemented, and ready for deployment as an endpoint, it also must be described clearly for service consumers. This is where WDSL comes in. Based on the service definition, the WSDL document describes the service, its operations, arguments, return types, and the schema for the data types used in them.

xrpcc Internals

The JAX-RPC reference implementation comes with the xrpcc (XML-based RPC Compiler) tool, which reads a tool-specific XML configuration file and generates the client- or server-side bindings shown in Figure 10.5. A developer can start with

Click To expand
Figure 10.5: xrpcc artifacts
  • A remote interface and use xrpcc to generate the stubs, ties, and WSDL

  • A WSDL document and generate the stubs to consume the service

  • A WSDL document and generate the stubs, ties, and remote interface and implement the service

Listing 10.3 shows the format for the XML configuration file xrpcc reads.

Listing 10.3: xrpcc configuration in the reference implementation
Start example
<?xml version="1.0" encoding="UTF-8"?>
<configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
<service name="" packageName="" targetNamespace="" typeNamespace="">
  <interface name="" servantName="" soapAction="" soapActionBase="">
         <handlerChains>
           <chain runAt=" " roles="">
                          <handler className="" headers="">
                           <property name="" value=""/>
                          </handler>
                       </chain>
                     </handlerChains>
</interface>
       <typeMappingRegistry>
                    <import>
                        <schema namespace="" location=""/>
                    </import>
                    <typeMapping encodingStyle="">
                         <entry schemaType=""
                                javaType=""
                                serializerFactory=""
                                deserializerFactory=""/>
                    </typeMapping>
                    <additionalTypes>
                         <class name=""/>
                    </additionalTypes>
                 </typeMappingRegistry>
                    <handlerChains>
                         <chain runAt="client" roles="">
                             <handler className=""headers="">
                                  <property name=""value=""/>
                             </handler>
                         </chain>
                         </handlerChains>
                         <namespaceMappingRegistry>
                              <namespaceMapping namespace=""
                                                packageName=""/>
                         </namespaceMappingRegistry>
              </service>
</configuration>

End example

Key XML elements of the configuration are discussed below. Some refer to concepts covered later in the chapter (e.g., handlers and typemappings).

Service Element. This describes the overall service. Only one service can be defined in the XML descriptor, to prevent potential name clashes in the generated code for the different services and the types they use.

  • name. The name of the service. This is also used as the value for the service element in the generated WSDL.

  • package. The package name for the generated service classes. xrpcc generates the stubs with the same package name as the service interface.

  • targetnamespace. The target namespace for the generated WSDL document.

  • typenamespace. The namespace for the schema portion of the generated WSDL document.

Interface element. This defines details about the interface the service supports. A service can have multiple interfaces.

  • name. Fully qualified name of an interface, such as com.flutebank.billpay.Billpay.

  • servant. Fully qualified name of the service interface implementation.

  • soapAction. Value to be used as the SOAPAction for all operations in the corresponding port (optional).

  • soapaActionBase. Value used as a prefix for the SOAPAction strings for the operations in the corresponding port (optional).

Handlerchain element. Defines information about handlers for this service. The handler element can be defined inside a service. If so, it is available to all interfaces inside the interface element, in which case it is specific only to that interface.

  • runAt. Defines where the handler is to be executed. Possible values are client or server.

  • roles. Lists or defines the roles that the handler will run as. This is the whitespace-separated List (xsd:anyURI)value returned by HandlerChain .getRoles().

  • className. Fully qualified name of the handler class.

  • headers. The header blocks processed by the handler. This is the whitespace-separated List(xsd:QName)-qualified name of a header block's outermost element.

  • property. Multiple and arbitrary name-value pairs the handler can use internally, such as configuration and initialization parameters. These properties are passed as input to Handler.init(HandlerInfo config) through the "config" argument. The HandlerInfo.getHandlerConfig() method returns a Map containing all property name-value pairs specified in the <property/> elements.

Typemapping registry element.

  • import. Specifies a list of schema documents to import and is used to generate the corresponding <wsdl:import/> and <schema:import/> elements.

  • typeMapping. Contains one or more entry elements.

  • entry. Specifies the encodingStyle, schemaType, Java class, and class for the serializer and deserializer factories.

  • additionalTypes. Specifies a list of Java classes that do not appear in the remote interface but are still passed to JAX-RPC. For example, if in a method with a signature

    public java.util.List getPaymentDetails() throws RemoteException;
    

    contains PaymentDetail objects, and the PaymentDetail class is not referenced by any other method in the remote interface, then for xrpcc to generate and register a serializer for the PaymentDetail type, this element must be specified:

    <additionalTypes>
         <class name="com.flutebank.PaymentDetail "/>
    </additionalType>
    

Namespace mapping registry.

  • namespaceMapping

The -both option of the xrpcc tool can be used to generate stubs and ties together. Alternatively, the server and client code can be generated separately, using the -server and -client options. Note that the -keep option must be used to retain the WSDL file.

One of the artifacts xrpcc generates when it reads the XML descriptor is an additional configuration file. So, what is this new configuration file? Remember, the service is being deployed in a servlet container, where the runtime-provided endpoint exists (the reference implementation defined the endpoint as a com.sun.xml.rpc.server.http.JAX-RPCServlet). This configuration file is used to hook the endpoint with the service implementation. It is just an implementation detail that, like xrpcc itself, is specific to the reference implementation and is not a part of the specifications. Other vendors may use a completely different tool with its own mechanism.

Java-WSDL Mappings

In Chapter 4, we discussed the WSDL structure, the role of vendor tools, and the significance of a standard specification to map WSDL elements to Java (and vice versa). To understand this mapping, let us revisit the role of WSDL elements from that chapter (Figure 10.6).

Click To expand
Figure 10.6: WSDL elements and dynamic interaction of a service and its consumer.

A Web service exposes groups of business operations for service consumers to use. Operations are grouped together to form portTypes. To invoke an operation, the consumer sends an input message containing the input data. It gets an output message containing the data that results from the business processing, or a fault if a problem occurs. The input and output messages may have multiple data items in them; each is called a part.

The wire protocol used for the invocation and the format of the input and output messages on the wire for that protocol are specified in a binding element. The service exposes itself to consumers through one or more ports, each of which specifies a network address where the service is located and the binding to use with that port. A service may render itself though several ports, where each port has a different binding (e.g., the same service may expose itself via SOAP/HTTP and SOAP/SMTP).

JAX-RPC defines the mapping of Java to WSDL data types, and vice versa. This is the mapping used by xrpcc when generating a WSDL file or consuming it. Table 10.6 summarizes this mapping. Listings 10.4 and 10.5 show a complete example of a service definition and its corresponding WSDL generated on the basis of these mappings.

Table 10.6: Data Type Mapping between Java and WSDL

Java type

WSDL mapping

Package

WSDL document

  • Sample extract code

  • The namespace definition in a WSDL is mapped to a Java package name.

Java type

WSDL mapping

Interface

wsdl:portType

  • Sample extract code

   public interface BillPay extends java.rmi.Remote {
   // methods here
   }

   <portType name="BillPay">
      // operations here
   </portType>

Java type

WSDL mapping

Method

wsdl:operation

  1. The WSDL operation name is the same as the method name.

  2. Overloaded methods can map to multiple operations with the same name or unique names that are implementation-specific.

  • Sample extract code

   public interface BillPay extends java.rmi.Remote {
      public PaymentDetail[] listScheduledPayments()
          throws RemoteException;
      public PaymentConfirmation schedulePayment(
          Date date, String payee, double amt)
       throws ScheduleFailedException, RemoteException;

   public double getLastPayment(String nickname) throws
                                      RemoteException;
   }

<portType name="BillPay">
   <operation name="listScheduledPayments">
     // input output messages for this operation
</operation>
   <operation name="schedulePayment">
     // input output messages for this operation
   </operation>
<operation name="getLastPayment">
     // input output messages for this operation
</portType>

Java type

WSDL mapping

Extended interface

wsdl:portType

with a complete set of inherited operations.

  • Sample extract code

   Public interface LinkedBillPay extends BillPay {
   public String getStatus() throws
   java.rmi.RemoteException, StatusUnavilableException;
   }
   <portType name="LinkedBillPay">
      <operation name="listScheduledPayments">
        // input output messages for this operation
   </operation>
      <operation name="schedulePayment">
        // input output messages for this operation
      </operation>
      <operation name="getStatus">
        // input output messages for this operation
      </operation>
      </operation>
   </portType>

Java type

WSDL mapping

Method arguments

wsdl:input and corresponding wsdl:message elements.

  • Sample extract code

   public interface BillPay extends java.rmi.Remote {
      public PaymentDetail[] listScheduledPayments()
          throws RemoteException;
   public PaymentConfirmation schedulePayment(Date
   date, String payee, double amt)
       throws ScheduleFailedException, RemoteException;
   public double getLastPayment(String nickname) throws
   RemoteException;
   }

     <portType name="BillPay">
       <operation name="getLastPayment" ">
         <input message="tns:BillPay_getLastPayment"/>
         // output message
       </operation>
           <operation name="listScheduledPayments">
             <input message="tns:BillPay_listScheduledPayments"/>
             // output message
       </operation>
         <operation name="schedulePayment" >
           <input message="tns:BillPay_schedulePayment"/>
           // output message
       </operation>
       </portType>

       <message name="BillPay_getLastPayment">
           <part name="String_1" type="xsd:string"/></message>
         <message name="BillPay_listScheduledPayments"/>
         <message name="BillPay_schedulePayment">
           <part name="Date_1" type="xsd:dateTime"/>
           <part name="String_2" type="xsd:string"/>
           <part name="double_3" type="xsd:double"/></message>

Java type

WSDL mapping

Method returns

wsdl:output and corresponding wsdl:message elements.

  • Sample extract code

   public interface BillPay extends java.rmi.Remote {
      public PaymentDetail[] listScheduledPayments()
          throws RemoteException;
   public PaymentConfirmation schedulePayment(Date date, String payee,
                                                           double amt)
       throws ScheduleFailedException, RemoteException;
   public double getLastPayment(String nickname) throws
   RemoteException;

   <message name="BillPay_getLastPaymentResponse">
      <part name="result" type="xsd:double"/></message>
   <message name="BillPay_listScheduledPaymentsResponse">
      <part name="result" type="tns:ArrayOfPaymentDetail"/></message>
   <message name="BillPay_schedulePaymentResponse">
      <part name="result" type="tns:PaymentConfirmation"/></message>
   <portType name="BillPay">
      <operation name="getLastPayment" parameterOrder="String_1">
           // input message here
      <output message="tns:BillPay_getLastPaymentResponse"/>
                                                          </operation>
      <operation name="listScheduledPayments" parameterOrder="">
           // input message here
          <output message="tns:BillPay_listScheduledPaymentsResponse"/>
                                                           </operation>
      <operation name="schedulePayment" parameterOrder="Date_1
                                                   String_2 double_3">
          <output message="tns:BillPay_schedulePaymentResponse"/>
   </operation></portType>

Java type

WSDL mapping

Checked exceptions

wsdl:fault

  1. wsdl:message name is the same as the exception name.

  2. RemoteExceptions are mapped to standard SOAP faults.

  3. The exception and its hierarchies get mapped to XML types in the schema, using the standard complexType extension mechanism.

  • Sample extract code

   public interface BillPay extends java.rmi.Remote {
      // other code
   public PaymentConfirmation schedulePayment(Date
   date, String payee, double amt)
       throws ScheduleFailedException, RemoteException;
   }
       <operation name="schedulePayment">
             // input and output elements
          <fault name="ScheduleFailedException">
             <soap:fault encodingStyle="http://schemas.xmlsoap.org/soap/
       encoding/" use="encoded" namespace="http://www.flutebank.com/xml"/>
                                                                  </fault>
             <soap:operation soapAction=""/>
       </operation>
       <message name="ScheduleFailedException">
          <part name="ScheduleFailedException" type=
                                 "tns:ScheduleFailedException"/></message>

Java type

WSDL mapping

Java identifiers

XML name.

  • Sample extract code

  • Java identifiers are already legal XML names.

   public class PaymentDetail {
     private String payeeName;
     private String account;
     private double amt;
     private Date date;
     // other code

   <types>
   // other code
   <complexType name="PaymentDetail">
           <sequence>
             <element name="date" type="dateTime"/>
             <element name="account" type="string"/>
             <element name="payeeName" type="string"/>
             <element name="amt" type="double"/></sequence>
                                                        </complexType>
   </types>

Listing 10.4: Source file for BillPay.java
Start example
package com.flutebank.billpayservice;

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

public interface BillPay extends Remote {

public PaymentConfirmation schedulePayment(Date date, String nickName, double amount)
throws ScheduleFailedException, RemoteException;

public PaymentDetail[] listScheduledPayments() throws RemoteException;

public double getLastPayment(String nickname) throws RemoteException;
}
End example
Listing 10.5: WSDL billservice.java corresponding to Listing 10.4
Start example
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="billpayservice" targetNamespace="http://www.flutebank.com/xml"
xmlns:tns="http://www.flutebank.com/xml" xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/
wsdl/soap/">
    <types>
        <schema targetNamespace="http://www.flutebank.com/xml" xmlns:wsdl="http://
schemas.xmlsoap.org/wsdl/" xmlns:tns="http://www.flutebank.com/xml" xmlns:xsi="http://
www.w3.org/2001/XMLSchema-instance" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/
encoding/" xmlns="http://www.w3.org/2001/XMLSchema">
            <import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
            <complexType name="ArrayOfPaymentDetail">
               <complexContent>
                  <restriction base="soap-enc:Array">
                        <attribute ref="soap-enc:arrayType" wsdl:arrayType=
                                                               "tns:PaymentDetail[]"/>
                  </restriction>
               </complexContent>
            </complexType>
            <complexType name="PaymentDetail">
               <sequence>
                  <element name="date" type="dateTime"/>
                  <element name="account" type="string"/>
                  <element name="payeeName" type="string"/>
                  <element name="amt" type="double"/>
                  </sequence>
               </complexType>
               <complexType name="PaymentConfirmation">
                  <sequence>
                     <element name="confirmationNum" type="int"/>
                     <element name="payee" type="string"/>
                     <element name="amt" type="double"/>
                     </sequence>
               </complexType>
               <complexType name="ScheduleFailedException">
                  <sequence>
                     <element name="message" type="string"/>
                     <element name="localizedMessage" type="string"/>
                  </sequence>
               </complexType>
            </schema>
        </types>
        <message name="BillPay_getLastPayment">
           <part name="String_1" type="xsd:string"/>
        </message>
        <message name="BillPay_getLastPaymentResponse">
           <part name="result" type="xsd:double"/>
        </message>
        <message name="BillPay_listScheduledPayments"/>
        <message name="BillPay_listScheduledPaymentsResponse">
           <part name="result" type="tns:ArrayOfPaymentDetail"/>
        </message>
        <message name="BillPay_schedulePayment">
           <part name="Date_1" type="xsd:dateTime"/>
           <part name="String_2" type="xsd:string"/>
           <part name="double_3" type="xsd:double"/>
        </message>
        <message name="BillPay_schedulePaymentResponse">
           <part name="result" type="tns:PaymentConfirmation"/>
        </message>
        <message name="ScheduleFailedException">
           <part name="ScheduleFailedException" type="tns:ScheduleFailedException"/>
        </message>
        <portType name="BillPay">
           <operation name="getLastPayment" parameterOrder="String_1">
              <input message="tns:BillPay_getLastPayment"/>
              <output message="tns:BillPay_getLastPaymentResponse"/>
           </operation>
           <operation name="listScheduledPayments" parameterOrder="">
              <input message="tns:BillPay_listScheduledPayments"/>
              <output message="tns:BillPay_listScheduledPaymentsResponse"/>
           </operation>
           <operation name="schedulePayment" parameterOrder="Date_1 String_2
                                                                            double_3">
              <input message="tns:BillPay_schedulePayment"/>
              <output message="tns:BillPay_schedulePaymentResponse"/>
              <fault name="ScheduleFailedException" message=
                                                       "tns:ScheduleFailedException"/>
           </operation>
           </portType>
        <binding name="BillPayBinding" type="tns:BillPay">
           <operation name="getLastPayment">
              <input>
                  <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                              use="encoded" namespace="http://www.flutebank.com/xml"/>
              </input>
              <output>
                 <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                              use="encoded" namespace="http://www.flutebank.com/xml"/>
             </output>
             <soap:operation soapAction=""/>
        </operation>
        <operation name="listScheduledPayments">
           <input>
              <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                              use="encoded" namespace="http://www.flutebank.com/xml"/>
           </input>
           <output>
                 <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                              use="encoded" namespace="http://www.flutebank.com/xml"/>
           </output>
              <soap:operation soapAction=""/>
        </operation>
        <operation name="schedulePayment">
           <input>
              <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                              use="encoded" namespace="http://www.flutebank.com/xml"/>
           </input>
           <output>
              <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                              use="encoded" namespace="http://www.flutebank.com/xml"/>
           </output>
           <fault name="ScheduleFailedException">
              <soap:fault encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
                              use="encoded" namespace="http://www.flutebank.com/xml"/>
           </fault>
           <soap:operation soapAction=""/>
        </operation>
        <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/>
   </binding>
   <service name="Billpayservice">
      <port name="BillPayPort" binding="tns:BillPayBinding">
         <soap:address location="http://127.0.0.1:9090/billpayservice/jaxrpc/BillPay"/>
      </port>
   </service>
</definitions>
End example

5. Service Consumption

Until now, we have seen how to define, implement, and deploy a JAX-RPC service. Let us now look at how such a service can be consumed. A service consumer represents the abstraction of the entity invoking the facilities of an existing service. Invocation modes for doing so fall into three broad categories:

  • Synchronous request-response. The client invokes a remote procedure and blocks until a response or an exception is received from the service. The client cannot do any other work while awaiting the response. This is analogous to making a phone call. Either someone responds by picking up the handset on the other end, or a busy tone is received.

  • One-way RPC. The client invokes a remote procedure but does not block or wait to receive a return and is free to do other work. In fact the client does not receive any return parameters. This is analogous to sending a fax (fire and forget!). When a fax is sent, a person does not need to pick up the phone on the receiving end for the fax to go through.

  • Nonblocking RPC invocation. The client invokes a remote procedure and continues processing without waiting for a return. The client may process the return later by polling some service or by using some other notification mechanism. This is analogous to making a phone call and getting an answering machine. The caller leaves a message and continues. The person on the other end gets the message and returns the call by dialing the number left on the machine or a number he or she already knows.

The significant difference between one-way and nonblocking invocation is that in the former, the client will not receive a return value.

As a bare minimum, JAX-RPC implementations must support the first two modes for client invocation and HTTP 1.1 as the transport binding for SOAP. The sematics of nonblocking RPC are quite complicated. For example, the client must inform the service of an endpoint to which the service can repond, and both parties must deal with issues of reliability and availability. If your application requires asynchronous communication, messaging is probably more appropriate. See Chapter 11 for details.

Let us now look at the mechanisms an RPC client can use to consume the service in these invocation modes. The client can be written to invoke the service using one of the following three mechanisms:

  • Stub

  • Dynamic invocation interface

  • Dynamic proxies

In Chapter 5, we described WSDL use cases and early/late binding patterns associated with them. The reader is encouraged to revisit that section before continuing. Recall the usage patterns:

  • Static compile-time binding

  • Static deploy-time binding

  • Static runtime binding

  • Dynamic binding

  • Dynamic binding with known location

The examples of clients in the following sections show how some of these patterns can be realized.

Clients Using Stubs

Figure 10.1 introduced the concept of stubs. Clients locate the service endpoint by specifying a URI, then simply invoke the methods on a local object, a stub that represents the remote service. JAX-RPC stubs, or proxies, as they are sometime referred to, are very different from RMI-IIOP stubs. Keep the following in mind:

  • A stub is never required to be downloaded or distributed to clients.

  • A client is not a required artifact on the client side. The end result of the invocation is that the required SOAP envelope must be sent on the transport protocol. The client can be written in a completely different programming language, as shown later in the JAX_RPC Interoperability section.

  • The stub is implemented in Java and is relevant only for a JAX-RPC client runtime.

  • A stub can be dynamically generated by the client side at runtime.

  • A stub is specific to the client runtime.

  • A stub is specific to a protocol and transport.

  • A stub must implement the javax.xml.rpc.Stub interface.

The tie represents the server-side skeleton for the implementation. It is used by the endpoint to communicate with the implementation and is generated using tools (such as xrpcc) when the implementation is deployed.

Using stubs is also sometime referred to as static invocation, because the stub must know the remote interface about the service at compile time. It must have the class file representing the remote interface and the implementation available for stub generation to proceed. The client does not need the WSDL file describing the service at runtime. Stubs are specific to a particular runtime and are not portable across vendor implementations.

The code in Listing 10.6 shows the fragment for invoking the Billpayservice developed previously.

Listing 10.6: Client using stubs
Start example
// import generated xrpcc classes + interface class + Helper classes for interface
  import com.flutebank.billpayservice.*;
  import java.util.Date;

public class StubClient {
    public static void main(String[] args) throws Exception {

   String endpoint="http://127.0.0.1:8080/billpayservice/jaxrpc/BillPay";
    String namespace = "http://www.flutebank.com/xml";
    String wsldport = "BillPayPort";
    Billpayservice_Impl serviceproxy= new Billpayservice_Impl();
    BillPay_Stub stub=(BillPay_Stub)(serviceproxy.getBillPayPort());
    stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY,endpoint);
    PaymentConfirmation conf= stub.schedulePayment(new Date(),
                                                         "my account at sprint", 190);
      System.out.println("Payment was scheduled "+ conf.getConfirmationNum());
      PaymentDetail detail[]=stub.listScheduledPayments();
     for(int i=0;i<detail.length;i++) {
           System.out.println("Payee name "+ detail[i].getPayeeName());
           System.out.println("Account "+ detail[i].getAccount());
           System.out.println("Amount "+ detail[i].getAmt());
           System.out.println("Will be paid on "+ detail[i].getDate());
           }
     double lastpaid= stub.getLastPayment("my cable tv provider");
     System.out.println("Last payment was "+ lastpaid);
   }
}
End example

Before using a stub, a client must first obtain a reference to it. The exact mechanism is specific to the implementation. The reference implementation for the stub is obtained by instantiating the service implementation class. The code below shows the mechanism another vendor might use:

InitialContext ctx = new InitialContext();
Billpayservice service =
      (Billpayservice) ctx.lookup("myserver:soap:Billpayservice");
BillPay bill = service. getBillPayPort ();
Stub stub= ((Stub) bill;

The stub can be configured by passing it name-value pairs of properties. The javax.xml.rpc.Stub interface defines four standard properties to configure the stub, using the stub. _setProperty(java.lang.String name, java.lang.Object value) method:

  • javax.xml.rpc.security.auth.username. Username for authentication.

  • javax.xml.rpc.security.auth.username.password. Password for authentication.

  • javax.xml.rpc.service.endpoint.address. Optional string for the endpoint service.

  • javax.xml.rpc.session.maintain. Use java.lang.Boolean to indicate that the server needs to maintain session for the client.

Clients Using DII

The second way a consumer can access a service involves the use of dynamic invocation interface (DII) instead of static stubs. DII is a concept that, like most other things in JAX-RPC, should be familiar to CORBA developers. Unlike static invocation, which requires that the client application include a client stub, DII enables a client application to invoke a service whose data types were unknown at the time the client was compiled. This allows a client to discover interfaces dynamically-in other words, at runtime rather than compile time-and invoke methods on objects that implement those interfaces.

JAX-RPC supports DII with the javax.xml.rpc.Call interface. A Call object can be created on a javax.xml.rpc.Service using the port name and service name. Then, during runtime, the following details are set:

  • Operation to invoke

  • Port type for the service

  • Address of the endpoint

  • Name, type, and mode (in, out, inout) of the arguments

  • Return type

This information is derived by looking at the WSDL file for the service. For example, the service name is the service name="Billpayservice"> element, the portname is the port name="BillPayPort" element, and so on. Listing 10.7 shows a DII client where a Call object is configured for the getLastPayment method.

Listing 10.7: Client using DII directly, where all parameters are known (WSDL is not passed)
Start example
import javax.xml.namespace.QName;
import javax.xml.rpc.Call;
import javax.xml.rpc.Service;
import javax.xml.rpc.ParameterMode;
import javax.xml.rpc.ServiceFactory;

public class DIIClient_NoWSDL{
  public static void main(String[] args) throws Exception {

     String endpoint="http://127.0.0.1:9090/billpayservice/jaxrpc/BillPay";
     String namespace = "http://www.flutebank.com/xml";
     String schemanamespace = "http://www.w3.org/2001/XMLSchema";
     String serviceName = "Billpayservice";

     ServiceFactory factory = ServiceFactory.newInstance();
     // the Billpayservice service does not exist
     // (no stub, skeleton, or Service was generated by xrpcc)
     // but createService will return a Service object
     // that can be used to create the dynamic call

    Service service = (Service) factory.createService
                                                   (new QName(namespace,serviceName));

     QName portName =      new QName(namespace," BillPayPort");
     QName operationName = new QName(namespace," getLastPayment");
     Call call = service.createCall(portName, operationName);
     call.setTargetEndpointAddress(endpoint);
     call.setProperty(Call.ENCODINGSTYLE_URI_PROPERTY,
                      "http://schemas.xmlsoap.org/soap/encoding/");

     QName paramtype = new QName(schemanamespace, "string");
     QName returntype = new QName(schemanamespace, "double");

     call.addParameter("String_1", paramtype, ParameterMode.IN);
     call.setReturnType(returntype);

     Object[] params = {"my cable tv provider"};
     Object lastpaid= (Double)call.invoke(params);
     System.out.println("Last payment was "+ lastpaid);
    }
}
End example

The client code wraps the DII request in a Call object. DII can be used directly, by passing these values (port, operation, location, and part information) to the Call, or indirectly, by passing the WSDL to the Call. Listing 10.7 shows how a DII client can be written using the former. (QName is a common class used to represent a qualified name in different XML APIs. The qualified name of an XML element consists of its namespace declaration and its local name in the namespace.)

What is relevant in Listing 10.7 is that there is no coupling between the service interface and the client (e.g., see import statements).

In indirect DII, only the port and operation names are knowm at compile time. The runtime will determine the type information about the part and location, based on the WSDL. In this case, the parameters and return types do not need to be configured using the addParameter or setReturnType method. Listing 10.8 shows a sample DII client using the WSDL.

Listing 10.8: Client using DII indirectly, where all parameters are not known (WSDL is dynamically inspected)
Start example
public class DIIClient_WSDL{

    public static void main(String[] args) throws Exception {

  String wsdllocation= http://127.0.0.1:9090/billpayservice/billpayservice.wsdl";

    String namespace = "http://www.flutebank.com/xml";
    String serviceName = "Billpayservice";

    ServiceFactory factory = ServiceFactory.newInstance();
    Service service = (Service) factory.createService
                      (new URL(wsdllocation),new QName(namespace,serviceName));

    QName portName = new QName(namespace," BillPayPort");
    QName operationName = new QName(namespace," getLastPayment");
    Call call = service.createCall(portName, operationName);
    Object[] params = {"my cable tv provider"};
    Object lastpaid= (Double)call.invoke(params);
    System.out.println("Last payment was "+ lastpaid);
   }
}
End example

Note that neither use of DII generates stubs.

WSDL with DII. When deciding whether to use WSDL or not in the client, keep in mind that though it may be more convenient to use, it requires an extra network call and processing overhead for the runtime to fetch and process the WSDL and perhaps even validate the call against the WSDL.

One of the major differences between static invocation and dynamic invocation is that, while both support synchronous communication, only DII supports one-way communication. From an API perspective, instead of using the invoke() method, DII can be used to invoke the invokeOneWay(java.lang.Object[] inputParams) method. Attempting to invoke a call.getOutputParams() in a one-way invocation will result in a JAX-RPCException.

Clients Using Dynamic Proxies

The JAX-RPC specification also specifies a third way for clients to access services: using the concept of dynamic proxy classes available in the standard J2SE Reflection API (the java.lang.reflect.Proxy class and the java.lang.reflect .InvocationHandler interface). A dynamic proxy class implements a list of interfaces specified at runtime. The client can use this proxy or façade as though it actually implemented these interfaces, although it actually delegates the invocation to the implementation.

Classes allowing any method on any of these interfaces can be called directly on the proxy (after casting it). Thus, a dynamic proxy class is used to create a type-safe proxy object for an interface list without requiring pregeneration of the proxy class, as you would with compile-time tools. Listing 10.9 shows how a client can use dynamic proxies.

Listing 10.9: Client using dynamic proxies
Start example
// jaxrpc classes
import javax.xml.namespace.QName;
import javax.xml.rpc.Service;
import javax.xml.rpc.ServiceFactory;
// java classes
import java.util.Date;
import java.net.URL;
// Interface class
import com.flutebank.billpayservice.BillPay;

public class DynamicProxyClient {
  public static void main(String[] args) throws Exception{
     String namespace = "http://www.flutebank.com/xml";
     String wsldport = "BillPayPort";
     String wsdlservice = "Billpayservice";
     String wsdllocation =
                          "http://127.0.0.1:8080/billpayservice/billpayservice.wsdl";
     URL wsldurl = new URL(wsdllocation);
     ServiceFactory factory = ServiceFactory.newInstance();
     Service service = factory.createService(wsldurl,
                                                  new QName(namespace, wsdlservice));
// make the call to get the stub corresponding to this service and interface
        BillPay stub = (BillPay) service.getPort(new QName(namespace,wsldport),
                                                                     BillPay.class);
// invoke methods on the service
        double lastpaid= stub.getLastPayment("my cable tv provider");
        System.out.println("Last payment was "+ lastpaid);
    }
}
End example

In Listing 10.9, there is no compile-time stub generation. The getPort() method will return the proxy, which is also required to implement the Stub interface at runtime-that is, the stub is generated internally at runtime. Again, CORBA developers will see the similarity in the above code with its counterpart:

BillPay stub = (BillPay)PortableRemoteObject.narrow(initial.lookup("Billpayservice"),
                                                                       BillPay.class);

Clients Using WSDL

Until now, we have seen how to start with a Java service definition and implement it as an XML-RPC Web service. One could also do the reverse:

  • Start with a WSDL file for an existing service and generate the stubs, to consume the service.

  • Start with a WSDL file and generate the ties (and stubs if needed) and remote interfaces, and fill in appropriate business logic to implement the service.

Let us look at how to consume the Billpayservice Web service using the service's WSDL and the WSDL 1.1-compliant xrpcc tool. The client-side bindings are generated from the WSDL using xrpcc, with only a configuration file change:

<?xml version="1.0" encoding="UTF-8"?>
      <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config">
          <wsdl location=http://127.0.0.1:9090/billpayservice/billpayservice.wsdl
                       packageName="generated">
                  </wsdl>
      </configuration>

The client-side code is identical to the StubClient shown previously, except that

  • The client no longer depends on the server interfaces but is coupled to the tool-generated classes-which, from the above configuration, reside in a generated package (see import statement).

  • The Date parameter has been changed to Calendar, as per the date type mappings from XML to Java specified in Tables 10.3a and 10.3b.

  • The endpoint does not need to be configured (unless you want to) and is picked from the soap:address element in the WSDL by the tool.

import generated.*; // generated classes by xrpcc from WSDL file
import java.util.Calendar;

public class WSDLClient {
    public static void main(String[] args) throws Exception {
        String namespace = "http://www.flutebank.com/xml";
        String wsldport = "BillPayPort";
        Billpayservice_Impl serviceproxy= new Billpayservice_Impl();
        BillPay_Stub stub=(BillPay_Stub)(serviceproxy.getBillPayPort());
        PaymentConfirmation conf=stub.schedulePayment(Calendar.getInstance(),
                                                "my account at sprint", 190);
        System.out.println("Payment was scheduled "+
                                                conf.getConfirmationNum());
        PaymentDetail detail[]=stub.listScheduledPayments();
        for(int i=0;i<detail.length;i++) {
          System.out.println("Payee name "+ detail[i].getPayeeName());
          System.out.println("Account "+ detail[i].getAccount());
          System.out.println("Amount "+ detail[i].getAmt());
          System.out.println("Will be paid on "+
                                              detail[i].getDate().getTime());
          }
          double lastpaid= stub.getLastPayment("my cable tv provider");
          System.out.println("Last payment was "+ lastpaid);
    }
}

What Client Is Right for Me?

Choosing either option shown above to implement the client affects only client-side development. When a server method is invoked, that server has no knowledge of whether a method was invoked via the conventional static stub mechanism, through DII, through proxies, or even by a non-Java client. From the server's perspective, it receives a SOAP request and generates a SOAP response; these are identical for all client types. For example, Listings 10.10a and 10.10b show SOAP request and response messages for the getLastPayment() method, which is identical for stubs, DII (with or without WSDL), dynamic proxies, or WSDL.

Listing 10.10a: SOAP request
Start example
POST /billpayservice/jaxrpc/BillPay HTTP/1.1
Content-Type: text/xml; charset="utf-8"
Content-Length: 506
SOAPAction: ""
User-Agent: Java1.3.1_01
Host: 127.0.0.1:9090
Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
Connection: keep-alive

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd=
"http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://
www.flutebank.com/xml" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<env:Body>
<ns0:getLastPayment>
    <String_1 xsi:type="xsd:string">my cable tv provider</String_1>
</ns0:getLastPayment>
</env:Body>
</env:Envelope>
End example
Listing 10.10b: SOAP response
Start example
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
SOAPAction: ""
Transfer-Encoding: chunked
Date: Mon, 29 Jul 2002 19:28:50 GMT
Server: Apache Coyote HTTP/1.1 Connector [1.0]

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd=
"http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance" xmlns:enc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns0="http://
www.flutebank.com/xml" env:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
   <env:Body>
      <ns0:getLastPaymentResponse>
          <result xsi:type="xsd:double">829.0</result>
      </ns0:getLastPaymentResponse>
   </env:Body>
</env:Envelope>
End example

In most practical situations, an enterprise will develop a service and publish its WSDL. Service consumers will use the WSDL and a vendor-provided tool (such as xrpcc) to generate client-side bindings and invoke the service. This has several advantages. There is no distribution of client code (e.g., remote interfaces), and in most cases, the tool will generate the serializers and deserializers, using the encoding scheme. For example, xrpcc generates the serializers and deserializers using the SOAP encoding scheme for PaymentDetail[] and PaymentDetail and maps the supported XML Schema types to Java.

Using stubs directly has the disadvantage of having to share the Java interface (and interface-dependent classes) with the service consumer. However, in scenarios where services will be developed within the boundaries of the enterprise, static stubs are the preferred client model, along the same lines as above. The performance with stubs is also expected to be better, since all type casting information is built in. All that occurs at runtime is service invocation.

DII is quite attractive, because it allows dynamic creation and invocation of object requests. In most cases, the architect of an application knows the kind of objects the application will need to access, and if not, WSDL should suffice. In some cases, such as object browsers and object brokers, DII is useful, but we don't envision these as frequent.

In practical situations and architecturally, DII is also not completely dynamic. Let us explain this further. Enterprise-level Web services will be coarse-grained and will frequently deal with passing data objects, such as the JavaBean's (e.g., PaymentDetail or PaymentConfirmation, as in the StubClient.java example). Simple data types will not suffice. For a DII client to be able to invoke these services, it will need the classes at compile time for the objects being passed around. (For example, if the DII code above invoked the schedulePayment method, the result would be a PaymentConfirmation object). The question is, where do these classes come from? The alternatives include using the same classes as the service, producing a coupling, or producing the classes from WSDL, using a tool (xrpcc). Further, if the data type that needs to be passed around is a custom type and not a JavaBean (e.g., a vector), a serializer and deserializer would need to be written for it. All this offsets the benefits DII offers of being a "dynamic invocation" at runtime.


Previous Section Next Section


JavaScript Editor Java Tutorials Free JavaScript Editor