Accessing Web Services From J2EE Components

Sean Brydon, Smitha Kangath

Problem Description

J2EE[TM] applications might need to access a web service. In these cases a J2EE component, for example a Servlet, JavaServer[TM] (JSP[TM]) page, Message Driven Bean, or Enterprise Bean, is acting as the client of a web service. The Java[TM] API for XML-based RPC (JAX-RPC) technology provides the standard way to access web services, and there are three modes to access a web service: stubs, dynamic proxies, and dynamic invocation interface (DII). This document will focus on the use cases where the client knows which service or set of services it will use, so we will not cover dynamically discovering and then accessing a service. The various strategies and their consequences for accessing web services are presented.

Some considerations are the following:

Solution

Consider some key issues when developing a J2EE component as a client of a web service. Note that the issues are mostly the same, whether you are using a web component such as a Servlet or using an EJB[TM] component.

First lets start with a brief overview of a J2EE component calling a web service. Feel free to skip the review section if you are familiar with the J2EE web service client programming model. After the review, some other issues for J2EE components accessing web services will be considered.

Overview of a J2EE Component Accessing a Web Service

This is the basic outline of a J2EE component calling a web service.
Basically, based on the target service WSDL file, a client will generate some artifacts such as a Java interface to the target service and supporting classes for objects such as the parameter types passed in the target service interface. The client deployment descriptor will need to include a service-ref for the service, the client code will look up that service reference through Java Naming and Directory Interface[TM] (JNDI), and then the client code will use the generated interface to call the service.

Below is a brief example code snippet to show a web component's code for accessing another application's web service. The code for an EJB component to access the target web service would be the same. In this case, the target web service's WSDL file described a service interface to accept XML purchase orders and has a method to submit the orders. The JAX-RPC classes, generated by the client based on the WSDL of the service, are a PurchaseOrderServiceSEI.java interface representing the target service, as well as a PurchaseOrder.java class which is the parameter type expected by the submitPO method of the service.

import javax.naming.*;
import javax.xml.rpc.*;

....
Context ic = new InitialContext();
Service purchaseOrderSvc =
    (Service) ic.lookup("java:comp/env/service/PurchaseOrderService");
PurchaseOrderServiceSEI port = (PurchaseOrderServiceSEI)
    purchaseOrderSvc.getPort(PurchaseOrderServiceSEI.class);

.....

//using the generated Purchase Order class for the parameter of
//the target service's submitPO method, create a Purchase Order
//object and set the values
PurchaseOrder po = new PurchaseOrder();
po.setPoId(poID);
...
//now call the service
String ret = port.submitPO(po);

Code Example 1: Code for a J2EE Component to Access a Web Service

Note that this snippet above, calling a web service is similar to calling other managed objects in J2EE. You use JNDI to look up and get a reference to a service object, then get a specific port object, and call methods on the port object. J2EE components run inside a container and therefore have access to the container services for looking up resources through JNDI.

The deployment descriptor needs a service reference element for the web service it accesses. For example, in a web.xml file for a Servlet or JSP component accessing a service, you would need one service reference for each service accessed. Below is a snippet from a web.xml file showing a service reference element.

<service-ref>
<description>String Purchase Order Service Client</description>
<service-ref-name>service/PurchaseOrderService</service-ref-name>
<service-interface>
com.sun.j2ee.blueprints.docoriented.client.poservice.PurchaseOrderService
</service-interface>
<wsdl-file>WEB-INF/wsdl/PurchaseOrderService.wsdl</wsdl-file>
<jaxrpc-mapping-file>WEB-INF/purchaseorderservice-mapping.xml</jaxrpc-mapping-file>
<service-qname xmlns:servicens="urn:PurchaseOrderService">servicens:PurchaseOrderService</service-qname>
</service-ref>

Code Example 2: Service Reference in a web.xml Deployment Descriptor

Also, in the application server-specific deployment descriptor, you need to map this service reference at assembly and deployment time to values in the deployment environment. For example, in the J2EE SDK, you use a sun-web.xml file. Below is an example sun-web.xml deployment descriptor.

<service-ref>
<service-ref-name>service/PurchaseOrderService</service-ref-name>
<port-info>
<service-endpoint-interface>
com.sun.j2ee.blueprints.docoriented.client.poservice.PurchaseOrderServiceSEI
</service-endpoint-interface>
<stub-property>
<name>javax.xml.rpc.service.endpoint.address</name>
<value>http://localhost:8080/webservice/PurchaseOrderServiceBean </value>
</stub-property>
</port-info>
</service-ref>

Code Example 3: Mapping of a Service Reference in a J2EE SDK sun-web.xml Deployment Descriptor

Issues for J2EE Components Accessing Web Services

Now that the basic programming model for a J2EE component to access a web service has been reviewed, let's consider some other issues.

Using WSDL

The process of designing and implementing the client starts by looking at the WSDL file for each service the client plans to access. The WSDL provides the client with the interface, the methods, the parameters, and the exceptions that will be needed to access the service. Additionally, some deployment information such as the service address may also be in the WSDL. Note that WSDL is not always fully descriptive of a service. At times, addition information is needed to understand the requirements of accessing a service. For example, WSDL has no interoperable way to specify that the service requires basic authentication or mutual authentication. So the developer of the client must obtain those service requirements from some source other than the WSDL file.

Another consideration is where the client gets and keeps WSDL. Because this discussion does not cover dynamic discovery, it assumes that the client developer knows where to obtain the WSDL file and can access it. The WSDL file for a service may be deployed and available at a URL, but the developer of the client code will need to make a copy of that WSDL file and make it part of the development workspace. Since the client starts with the WSDL, generally the development style of the client artifacts needed to programatically access the service will be WSDL to Java. Based on the WSDL file, the client development tools will generate some Java classes representing the service described in the WSDL file. The J2EE component that makes calls to this target service will use these generated classes. As well as using the WSDL file at build time, the client application will need to bundle the WSDL file as part of the portable packaging.

Another consideration is that if the developer of the client code uses the WSDL file to generate some client-side classes to represent the target service interface, the Java types that correspond to the types in the service WSDL file may be different than one might expect. You could get a Java Calendar type when you might expect a Date type. The mapping of the WSDL types to Java types may vary a bit among tools and versions of JAX-RPC.

Adding Structure and Using Patterns

Just like the design for any application, the design for a web service client can benefit from some structure. It is a good idea for client code to separate the web service interaction code from the rest of the client code. Applying the Business Delegate code to create to encapsulate the web service access from the rest of the code is a recommended practice. The Business Delegate encapsulates the access to the web service and helps enforce a separation between the web service interaction code from the rest of the client code. Using the ServiceLocator pattern can also help clean up the code and make the application more manageable by encapsulating common lookup code. For more detail, the usage of the service locator for web services is covered in detail in another entry in the Java BluePrints Solutions catalog.

Choosing the Communication Mode

For J2EE components that access web services, it is best to use the stubs obtained through a JNDI lookup. However, the access code can be written such that it does not depend on the generated stub class. This is important since the stub classes are not portable across application servers.The code example 4 below shows how a J2EE application might use a stub to access a service. The application locates the service using a JNDI InitialContext.lookup call. The JNDI call returns an OpcOrderTrackingService object, which is a stub.

  Context ic = new InitialContext();
  StringPurchaseOrderService_Impl svc = (StringPurchaseOrderService_Impl) ic.lookup("java:comp/env/service/StringPurchaseOrderService");
  StringPurchaseOrderServiceSEI poservice = svc.getStringPurchaseOrderServiceSEIPort();
  String purchaseOrder = ... // This is the string representation of a purchase order in XML
   String result = poservice.submitPO(purchaseOrder);

Code Example 4: Accessing a Stub in a J2EE Environment (Not a Recommended Approach)

Because it depends on the generated stub classes, the client code above is not the recommended strategy for using stubs. Although this example works without problems, JAX-RPC gives you a neutral way to access a service and obtain the same results. By using the JAX-RPC javax.xml.rpc.Service interface method getPort, you can access a web service in the same manner regardless of whether you use stubs or dynamic proxies. The getPort method returns either an instance of a generated stub implementation class or a dynamic proxy, and the client can then uses this returned instance to invoke operations on the service endpoint. Code Example 5 illustrates how to remove the dependency on the generated stub classes.

  Context ic = new InitialContext();
  Service svc = (Service) ic.lookup("java:comp/env/service/StringPurchaseOrderService");
 StringPurchaseOrderServiceSEI poservice = (StringPurchaseOrderServiceSEI) svc.getPort(StringPurchaseOrderServiceSEI.class);
  String purchaseOrder = ... // This is the string representation of a purchase order in XML
   String result = poservice.submitPO(purchaseOrder);

Code Example 5: Accessing a Stub in a J2EE Environment (Recommended Approach)

Rather than cast the JNDI reference to the service implementation class, the code casts the JNDI reference to a javax.xml.rpc.Service interface. The client-side representation of the service endpoint interface is obtained by invoking the getPort method. After obtaining the port, the client may make any calls desired by the application on the port. When the above code is invoked, the JAX-RPC runtime selects a port and protocol binding for communicating with the port, then configures the returned stub that represents the service endpoint interface. Furthermore, because the J2EE platform allows the deployment descriptor to specify multiple ports for a service, the container, based on its configuration, can choose the best available protocol binding and port for the service call.

Binding to the Target Service URL

A target web service is deployed at a URL, and to access that service the client code must bind the service reference to that URL. Depending on the requirements of the application, the client can bind a service reference to the service URL at various times.

Handling Exceptions

Two types of exceptions occur for client applications that access web services: system exceptions and service-specific exceptions, which are thrown by a service endpoint. The client application must deal with both types of exceptions.

System exceptions are thrown for such conditions as passing incorrect parameters when invoking a service method, service inaccessibility, network error, or some other error beyond the control of the application. Generally, there is not much the client can do about system exceptions except perhaps retry the call to the service. A client of a web service will usually use a tool and have JAX-RPC generate some client-side interface that maps to the WSDL file of the target service. These generated interfaces to be used in the client code will throw Remote exceptions for system exceptions. The code using this interface to call the service needs to catch these Remote exceptions.

public interface PurchaseOrderServiceSEI extends java.rmi.Remote {
    public java.lang.String submitPO(
PurchaseOrder purchaseOrder_1) throws java.rmi.RemoteException;
}
Code Example 6: Generated Client-Side Interface Corresponding to a Service, Showing System Exceptions Being Mapped to Remote Exceptions

Service exceptions, which are mapped from faults, are thrown when a service-specific error is encountered. Service exceptions are defined by the target web service and exposed as part of the WSDL file. An example of a service-specific exception would be that a validation of the data submitted revealed an error and it throws an invalid data exception. When a developer generates the client-side interfaces and classes corresponding to the target service WSDL file, the service exceptions will also be generated as a client-side representation of the service exception. In many cases the client code can take corrective action and resubmit a request with corrected data so these service-specific exception are recoverable. The important point to note is that when designing a client to access a service, you need to account for these different types of exceptions specified in the target service WSDL file and design the client code to handle them.

Packaging for Portability

J2EE components acting as clients of a web service are packaged just like other J2EE applications. The important point to note is that these applications with J2EE components that access a web service are portable. The packaging required is fairly straight forward. If the client is a web component then it is packaged up in a war file with the typical web artifacts such as a web.xml file as well as a few additional artifacts specific to the web service access code. Likewise, an EJB component is packaged in an ejb-jar file with the typical EJB artifacts such as an ejb-jar.xml file as well as a few additional artifacts specific to the web service access code.

Web service clients running in a J2EE environment require these additional artifacts, as follows:

Handling Security Requirements

A target web service may have security requirements. The security requirements are sometimes specified in the WSDL file, and sometimes are not specified in the WSDL file even though the service endpoint may expect them. In any case, if the target service has some security requirements then the client must deal with them. Some example security requirements are that the target endpoint is available over SSL, the target service specifies basic authentication and so expects the client to provide a user name and password, or the target service expects mutual authentication so the client also needs to provide a digital certificate. Additionally, the target service may use some message-level security. The client configuration for handling these scenarios (providing a user name and password, handling the target service's certificate for SSL, providing the client's digital certificate to the target service for mutual authentication, and so on) is all application server-specific, so the details will not be covered here. Refer to your application server's documentation for configuring the security environment when making calls to a web service.

Although the client code can handle the security requirements of a target service by providing the necessary security artifacts in the client code, you should avoid handling the security in the client code. Instead, the client should use the application server configuration tools to configure a service reference to handle the security requirements of a target service. This enables the J2EE container to manage the security for a configured service reference and provide the necessary artifacts at runtime when a call is made to the target service. An example of code handling a security requirement of a target service is when the service has specified basic authentication and the client code sets the username and password on the Stub properties. The recommended alternative is to configure the application server to manage the basic authentication for the service reference of the client application. This way, the providing of the username and password will be handled by the application server instead of by the client code. This approach keeps the code cleaner.

References

For more information about this topic, refer to the following:
© Sun Microsystems 2005. All of the material in The Java BluePrints Solutions Catalog is copyright-protected and may not be published in other works without express written permission from Sun Microsystems.