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:
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.
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.
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.
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.
Now that the basic programming model for a J2EE component to access a web service has been reviewed, let's consider some other issues.
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.
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.
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)
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.
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.
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.
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:
service-ref
element in the
deployment descriptor (web.xml or ejb-jar.xml)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.