Support for web services in EJB 2.1 is based on the web services for the J2EE 1.1 (WS-J2EE) specification. This specification includes the Java API for XML-based RPC (JAX-RPC), SOAP with Attachments API for Java (SAAJ), and the Java API for XML Registries (JAXR). JAX-RPC is basically Java RMI over SOAP; SAAJ is an API for manipulating the structure of a SOAP message; and JAXR allows you to access web service registries, usually UDDI (Universal Description, Discovery and Integration).
While this chapter and the one before it provide you with a launching pad for learning about web services in J2EE (specifically EJB), the subject is too huge to cover in a book about EJB. In order to cover J2EE web services comprehensively we would have needed another 500 pages—since you’ll need to lift this book to read it, I wrote a lighter approach to the subject. This chapter provides you with an introduction to JAX-RPC, but it should not be considered a comprehensive guide to the API.
If you are interested in learning more about the standard web services technologies (XML, SOAP 1.1, WSDL, and UDDI) and J2EE APIs (JAX-RPC, SAAJ, and JAXR), you might want to read J2EE Web Services (Addison-Wesley) by the author of this book, for a complete and thorough coverage of these topics.
The main purpose of JAX-RPC is to describe the relationship between WSDL 1.1, XML, SOAP 1.1, and Java. JAX-RPC provides EJB with a client-side programming model for accessing remote web services, as well as a server-side programming model for deploying EJBs as web services.
JAX-RPC provides a client-side programming model based on Java RMI that allows you to access web services on other platforms from your EJBs. In other words, by using JAX-RPC, EJBs can access web services across the network hosted on Java and non-Java platforms (Perl, .NET, C++, and so on) alike. There are three APIs for accessing web services: generated stubs, dynamic proxies, and the DII (Dynamic Invocation Interface). Of these three APIs, the one you are most likely to use is the Generated Stubs programming model, which is the primary focus of this chapter.
Generated stubs are based on the classic Java RMI programming model, where the client accesses a remote service via a Java RMI remote interface implemented by a network stub. The stub translates calls made on the remote interface into network messages sent to the remote service. It’s pretty much the same as using an EJB remote reference, except the protocol is SOAP over HTTP rather than CORBA IIOP. Figure 15-1 illustrates the RMI loop executed with a JAX-RPC generated stub.
The RMI loop in JAX-RPC is basically the same as any other RMI loop. In step 1, the client invokes a method on the JAX-RPC generated stub. The method invocation is transformed into a SOAP message that is sent to the server in step 2. In step 3 the web service process the request and send the results back as a SOAP response message in step 4. In step 5, the SOAP response messages is transformed into either a return value or an exception (if it was a SOAP Fault) and returned to the client.
Generated
stubs get their name because the
remote interface, called an endpoint
interface
, and the network stub are generated
at deployment time. A JAX-RPC-compliant compiler generates the
endpoint interface and stub from a WSDL document. The WSDL
<portType>
is used to create an endpoint
interface, while the WSDL <binding>
and
<port>
definitions are used to create the
stub. The WSDL document is provided by the organization that hosts
the web service. The JAX-RPC compiler reads the WSDL document and
translates it into an endpoint interface and stub that you can use at
runtime to send and receive SOAP messages.
Imagine that Titan Cruises subcontracts a company, Charge-It, Inc., to process payments made by customers using credit cards. Charge-It runs a system based on .NET and exposes its credit card processing application to clients via a web service. The web service is described by a WSDL document. The WSDL document for Charge-It’s web service looks like this:
<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://charge-it.com/Processor" targetNamespace="http://charge-it.com/Processor"> <message name="chargeRequest"> <part name="name" type="xsd:string"/> <part name="number" type="xsd:string"/> <part name="exp-date" type="xsd:dateTime"/> <part name="card-type" type="xsd:string"/> <part name="amount" type="xsd:float"/> </message> <message name="chargeResponse"> <part name="return" type="xsd:int"/> </message> <portType name="Processor"> <operation name="charge"> <input message="tns:chargeRequest"/> <output message="tns:chargeResponse"/> </operation> </portType> <binding name="ProcessorSoapBinding" type="tns:Processor"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="charge"> <soap:operation soapAction="" style="rpc"/> <input> <soap:body use="literal" namespace="http://charge-it.com/Processor"/> </input> <output> <soap:body use="literal" namespace="http://charge-it.com/Processor"/> </output> </operation> </binding> <service name="ProcessorService"> <port name="ProcessorPort" binding="tns:ProcessorSoapBinding"> <soap:address location="http://www.charge-it.com/ProcessorService"/> </port> </service> </definitions>
The endpoint interface is based on the WSDL
<portType>
and its corresponding
<message>
definitions. Based on these
definitions, a JAX-RPC compiler would generate the following
interface:
package com.charge_it; public interface Processor extends java.rmi.Remote { public int charge(String name, String number, java.util.Calendar expDate, String cardType, float amount) throws java.rmi.RemoteException; }
An endpoint
interface is a Java RMI remote interface that extends the
java.rmi.Remote
type. Its methods must throw the
java.rmi.RemoteException
and, optionally,
application exceptions. The interface name, method names, parameters,
and exceptions are all derived from the WSDL document. Figure 15-2 shows the mapping between the
<portType>
and
<message>
definitions and the endpoint
interface.
The name of the endpoint interface comes from the name of the
<portType>
, which is
Processor
. The methods defined by the endpoint
interface are derived from the <operation>
elements declared by the WSDL <portType>
. In
this case, there is one <operation>
element,
which maps a single method, charge( )
. The
parameters of the charge( )
method are derived
from <operation>
element’s
input message. For each <part>
element of
the input message, there will be a corresponding parameter in the
charge( )
method. The output message, in this
case, declares a single <part>
element,
which maps to the return type of the charge( )
method.
The JAX-RPC specification
defines an exact mapping between many of the XML Schema built-in
types and Java. This is how the XML Schema types declared by the WSDL
<part>
elements are mapped to the parameters
and the return type of an endpoint method. Table 15-1 shows the mapping between XML Schema built-in
types and Java primitives and classes.
Table 15-1. XML Schema built-in types and their corresponding Java types
XML Schema built-in type |
Java type |
---|---|
xsd:byte |
byte |
xsd:boolean |
boolean |
xsd:short |
short |
xsd:int |
int |
xsd:long |
long |
xsd:float |
float |
xsd:double |
double |
xsd:string |
java.lang.String |
xsd:dateTime |
java.util.Calendar |
xsd:integer |
java.math.BigInteger |
xsd:decimal |
java.math.BigDecimal |
xsd:QName |
java.xml.namespace.QName |
xsd:base64Binary |
byte [ ] |
xsd:hexBinary |
byte [ ] |
JAX-RPC also maps
nillable types (types that can be null), based
on XML Schema built-in types, to Java primitive wrappers. For
example, a nillable xsd:int
type would map to a
java.lang.Integer
type and a nillable
xsd:double
would map to a
java.lang.Long
type.
In addition, JAX-RPC defines a mapping between
complex types
defined in the WSDL <types>
element and Java
bean classes. Complex types are addressed later in this chapter.
The stub, which implements the endpoint interface, is generated from
the <binding>
and
<port>
definitions. The JAX-RPC compiler
translates the messaging style specified by the
<binding>
definition into a marshalling
algorithm for converting method calls made on the endpoint stub into
SOAP request and reply messages. Charge-It’s WSDL
document defines the following <binding>
element:
<binding name="ProcessorSoapBinding" type="tns:Processor"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="charge"> <soap:operation soapAction="" style="rpc"/> <input> <soap:body use="literal" namespace="http://charge-it.com/Processor"/> </input> <output> <soap:body use="literal" namespace="http://charge-it.com/Processor"/> </output> </operation> </binding>
According to the <binding>
element, the web
service employs RPC/Literal SOAP 1.1 messages with a request-response
style operation. When the JAX-RPC compiler reads this
<binding>
, it generates a corresponding stub
that implements the endpoint interface. The stub is responsible for
converting method calls made on the endpoint interface into SOAP
messages sent to the web service. It’s also
responsible for converting SOAP response messages sent back to the
stub into a return value—or, if it’s a SOAP
fault message, into an exception thrown by the endpoint method.
The stub is also based on a particular
<port>
definition, which declares the
Internet address where the web service is located. The Charge-It WSDL
document defines the following <port>
element:
<service name="ProcessorService"> <port name="ProcessorPort" binding="tns:ProcessorSoapBinding"> <soap:address location="http://www.charge-it.com/ProcessorService"/> </port> </service>
Based on this <port>
definition, the JAX-RPC
compiler generates the stub that exchanges SOAP messages with the URL
indicated by the address attribute
(http://www.charge-it.com/ProcessorService
). Figure 15-3 illustrates how the Processor endpoint
interface and stub are used to access the Charge-It credit card
processing web service.
In addition to the endpoint interface and its stub, the JAX-RPC
compiler also creates a Service interface, which is used to get an
instance of the generated stub at runtime. The Service interface is
based on the <service>
element of the WSDL
document and declares methods for obtaining a live endpoint stub.
Here’s the definition of the
ProcessorService
interface generated from
Charge-It’s WSDL document:
package com.charge_it;
public interface ProcessorService extends javax.xml.rpc.Service {
public com.charge_it.Processor getProcessorPort( )
throws javax.xml.rpc.ServiceException;
public java.lang.String getProcessorPortAddress( );
public com.charge_it.Processor getProcessorPort(java.net.URL portAddress)
throws javax.xml.rpc.ServiceException;
}
The getProcessorPort( )
method returns a live
endpoint stub that is ready to invoke methods on the web service. The
getProcessPortAddress( )
method returns the URL
that the stub accesses by default. The
getProcessorPort(URL)
method allows you to create
an endpoint stub that accesses a URL that is different from the
default URL defined in the WSDL document.
The JAX-RPC compiler also generates a class that implements the Service interface. This class is tightly bound to the EJB Container system and manufactures endpoint stubs at runtime.
Just like other resources (JDBC, JMS, and so on) the JAX-RPC Service is bound to a specific namespace in the JNDI ENC at deployment time. To get a reference to a stub at runtime, therefore, the EJB requests a specific JAX-RPC Service from the JNDI ENC. The stub is then used to execute operations on the remote web service.
To illustrate how stubs are used by EJBs, we will modify the
bookPassage( )
method of the
TravelAgentBean
defined in Chapter 11. Instead of using the ProcessPayment EJB to
process credit cards, the TravelAgent EJB will use the
Charge-It’s Processor web service. The following
code shows the changes to the TravelAgentBean
class:
package com.titan.travelagent;import com.charge_it.ProcessorService;
import com.charge_it.Processor;
... public class TravelAgentBean implements javax.ejb.SessionBean { public CustomerRemote customer; public CruiseLocal cruise; public CabinLocal cabin; public javax.naming.Context jndiContext; ... public TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState( ); } try { ReservationHomeLocal resHome = (ReservationHomeLocal) jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); ReservationLocal reservation = resHome.create(customer, cruise, cabin, price, new Date( ));ProcessorService webService = (ProcessorService) jndiContext.lookup(
"java:comp/env/service/ChargeItProcessorService");
Processor endpointStub = webService.getProcessorPort( );
String customerName = customer.getFirstName( )+" "+ customer.getLastName( ); java.util.Calandar expDate = new Calandar(card.date);endpointStub.charge(customerName, card.number,
expDate, card.type, price);
TicketDO ticket = new TicketDO(customer, cruise, cabin, price); return ticket; } catch(Exception e) { throw new EJBException(e); } } ... }
As you can see, the EJB uses the JAX-RPC endpoint stub much like it would any other resource. It obtains a reference to a resource factory from the JNDI ENC, uses that to obtain the stub, and uses the stub to invoke operations on the web service—in this case, Charge-It’s Processor web service.
The stub, however, presents some
problems in a transactional environment. If the stub encounters a
networking problem or SOAP processing error, it throws a
JAXRPCException
, which is caught and rethrown as
an EJBException
, causing the entire transaction to
roll back. However, if an error occurs after the web service has
executed but before the EJB method successfully returns, a partial
rollback occurs: the reservation would be rolled back, but the charge
made using the Charge-It web service would not.
EJB 2.1 includes a new element,
<service-ref>
, which binds a JAX-RPC Service
to the JNDI ENC. The modified TravelAgent EJB declares a
<service-ref>
element that looks like this:
<?xml version='1.0' encoding='UTF-8' ?> <ejb-jar xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:chargeIt="http://charge-it.com/Processor"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1"> <enterprise-beans> <session> <ejb-name>TravelAgentEJB</ejb-name> ...<service-ref>
<service-ref-name>
service/ChargeItProcessorService</service-ref-name>
<service-interface>
com.charge_it.ProcessorService</service-interface>
<wsdl-file>
META-INF/wsdl/ChargeItProcessor.wsdl</wsdl-file>
<jaxrpc-mapping-file>
META-INF/mapping.xml</jaxrpc-mapping-file>
<service-qname>
chargeIt:ProcessorService</service-qname>
</service-ref>
... </session> </enterprise-beans> ... </ejb-jar>
The <service-ref-name>
element declares the
name of the JAX-RPC Service in the JNDI
ENC—it’s always relative to the
"java:comp/env
" context. The
<service-interface>
identifies the JAX-RPC
Service interface, which is implemented by a JAX-RPC service object.
The <wsdl-file>
identifies the location of
the WSDL document that describes the Charge-It web service. The WSDL
document must be packaged in the same EJB-JAR file as the EJB that is
making the web service call. The path is always relative to the root
of the EJB-JAR file. In this case, a copy of the Charge-It WSDL
document, ChargeItProcessor.wsdl
, is stored in the
META-INF directory of the EJB-JAR file. The
<jaxrpc-mapping-file>
identifies the location of the JAX-RPC
mapping file relative to the root of the EJB-JAR file. In this case,
it’s also located in the
META-INF directory. (The JAX-RPC
mapping file is an additional deployment file that helps
the EJB container system understand the mapping between the WSDL
document and the endpoint service interfaces.) The
<service-qname>
identifies the fully
qualified XML name of the WSDL <service>
definition to which this reference pertains. The qualified service
name is relative to the WSDL document identified by the
<wsdl-file>
element.
A JAX-RPC mapping file is required if an EJB is to use JAX-RPC to access web services. The mapping file conforms to a specific XML Schema defined by the Web Services for J2EE 1.1 specification. This file helps the deployment tools and EJB container understand the relationship between a JAX-RPC service and endpoint interfaces, and their corresponding WSDL document, allowing the deployment tools to generate a proper stub: one that uses the correct protocols and messaging modes.
At a bare minimum, the JAX-RPC mapping file must specify the mapping
between the WSDL XML namespace of a
<service>
element and a Java package name.
Example 15-1 is a perfectly legal JAX-RPC mapping
file for the <service-ref>
used by the
TravelAgent EJB.
Example 15-1. EJB 2.1: Lightweight JAX-RPC mapping file
<?xml version='1.0' encoding='UTF-8' ?> <java-wsdl-mapping xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_jaxrpc_mapping_1_1.xsd" version="1.1"> <package-mapping> <package-type>com.charge_it</package-type> <namespaceURI>http://charge-it.com/Processor</namespaceURI> </package-mapping> </java-wsdl-mapping>
The JAX-RPC mapping shown in the previous listing is as simple as it gets. Only under very specific conditions can a JAX-RPC mapping file be this simple; the TravelAgent EJB happens to use a web service that qualifies. Here’s a brief list of the attributes a WSDL document must have in order to qualify for a package-only JAX-RPC mapping file:
It has only one <service>
element, which
contains one <port>
element.
The <service>
,
<binding>
,
<portType>
, and all custom XML types
(complexType
and simpleType
)
have unique names.
The <binding>
definition uses the RPC
messaging style (style="rpc
“) and SOAP 1.1
Encoding
(encodingStyle="http://schemas.xmlsoap.org/soap/encoding/
“)
for all input, output, and fault message parts.
No header blocks or header faults are specified in the
<binding>
definition; the
parts
attribute of input and output elements must
be omitted or, if the parts attribute is declared, it must list all
parts.
Each <operation>
within a
<portType>
definition must:
Have a unique name.
Include exactly one <input>
element, zero or
one <output>
elements, and zero or more
<fault>
elements.
Omit the parameterOrder
attribute. If the
parameterOrder
is declared, the
<operation>
must specify all parts from the
input message in the order they are originally declared in the
corresponding <message>
definition.
A fault <message>
definition has one part
named "message
" of type
"xsd:string
“.
The input <message>
definition may declare
zero or more <part>
elements, and the output
<message>
definition may declare zero or one
<part>
elements.
Every <part>
definition is defined with a
name
attribute and a type
attribute; the element
attribute is not used. The
type
attribute may be one of the following:
A standard XML Schema built-in type
An XML Schema-based complex type, which uses either the
xsd:sequence
or xsd:all
compositor and can be easily mapped to Java beans according to the
JAX-RPC specifications
A WSDL-restricted SOAP Encoded array
The ChargeItProcessor.wsdl document meets all these requirements; as a result, it only needs to have a package mapping. It’s not difficult to create WSDL documents that meet these requirements; however, if you are attempting to access a web service defined by someone else, you’re likely to run into WSDL documents that do not adhere to the criteria for a lightweight mapping file. In that case, you’ll have to create a heavyweight mapping file. Example 15-2 is a heavyweight mapping file for the ChargeItProcessor.wsdl document.
Example 15-2. Heavyweight JAX-RPC mapping file
<?xml version='1.0' encoding='UTF-8' ?> <java-wsdl-mapping xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:chargeIt="http://charge-it.com/Processor" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_jaxrpc_mapping_1_1.xsd" version="1.1"> <package-mapping> <package-type>com.charge_it</package-type> <namespaceURI>http://charge-it.com/Processor</namespaceURI> </package-mapping> <service-interface-mapping> <service-interface>com.charge_it.ProcessorService</service-interface> <wsdl-service-name>chargeIt:ProcessorService</wsdl-service-name> <port-mapping> <port-name>chargeIt:ProcessorPort</port-name> <java-port-name>ProcessorPort</java-port-name> </port-mapping> </service-interface-mapping> <service-endpoint-interface-mapping> <service-endpoint-interface>com.charge_it.Processor </service-endpoint-interface> <wsdl-port-type>chargeIt:Processor</wsdl-port-type> <wsdl-binding>chargeIt:ProcessorSoapBinding</wsdl-binding> <service-endpoint-method-mapping> <java-method-name>charge</java-method-name> <wsdl-operation>chargeIt:charge</wsdl-operation> <method-param-parts-mapping> <param-position>0</param-position> <param-type>java.lang.String</param-type> <wsdl-message-mapping> <wsdl-message>chargeIt:chargeRequest</wsdl-message> <wsdl-message-part-name>name</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <method-param-parts-mapping> <param-position>1</param-position> <param-type>java.lang.String</param-type> <wsdl-message-mapping> <wsdl-message>chargeIt:chargeRequest</wsdl-message> <wsdl-message-part-name>number</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <method-param-parts-mapping> <param-position>2</param-position> <param-type>java.util.Calandar</param-type> <wsdl-message-mapping> <wsdl-message>chargeIt:chargeRequest</wsdl-message> <wsdl-message-part-name>exp-date</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <method-param-parts-mapping> <param-position>3</param-position> <param-type>java.lang.String</param-type> <wsdl-message-mapping> <wsdl-message>chargeIt:chargeRequest</wsdl-message> <wsdl-message-part-name>card-type</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <method-param-parts-mapping> <param-position>4</param-position> <param-type>float</param-type> <wsdl-message-mapping> <wsdl-message>chargeIt:chargeRequest</wsdl-message> <wsdl-message-part-name>amount</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <wsdl-return-value-mapping> <method-return-value>int</method-return-value> <wsdl-message>chargeIt:chargeResponse</wsdl-message> <wsdl-message-part-name>return</wsdl-message-part-name> </wsdl-return-value-mapping> </service-endpoint-method-mapping> </service-endpoint-interface-mapping> </java-wsdl-mapping>
The complete JAX-RPC mapping file is too complicated to discuss in
detail. Suffice it to say, the heavyweight mapping file is complex
and provides elements for mapping every aspect of the service and
endpoint interfaces to a WSDL document. The service interface is
mapped to a WSDL <service>
element, the
endpoint interface is mapped to a WSDL
<portType>
, each method is mapped to a WSDL
<operation>
, and every parameter and return
value is mapped to a specific WSDL <part>
of
a specific WSDL <message>
definition.
It seems to me that a JAX-RPC compiler should be able to interpret a far broader set of WSDL definitions than the very narrow criteria required for a lightweight mapping. The Web Services for J2EE specification requires a complete mapping for any JAX-RPC resource that strays even a little from the minimum criteria for a lightweight mapping. In my opinion, the criteria should be broadened. Only the nonconforming aspects of the WSDL document should be mapped; conforming elements should not require documentation in the mapping file.
Exercise 15.1 in the Workbook shows how to deploy these examples.