Feb. 15, 2009, 10:33 a.m.
posted by hashspark
Accessing Web Services with JAX-RPCJAX-RPC provides a client-side programming model 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 both Java and non-Java platforms (Perl, .NET, C++, and so on). There are three APIs for accessing web services: generated stubs, dynamic proxies, and the Dynamic Invocation Interface (DII). When JAX-RPC is accessed from a Java EE/EJB 3.0 environment, the decision to use a dynamic proxy or a stub is up to the specific container on which you are running. In this case, however, a dynamic proxy will most likely be used because a stub is not portable between Java EE platforms. A dynamic proxy is very much like the classic Java RMI or CORBA programming model, where the client accesses a remote service via a remote interface implemented by a network stub. The stub translates calls made on the remote interface into network messages that are sent to the remote service. It's similar to using an EJB remote reference; however, the protocol is SOAP over HTTP rather than CORBA IIOP. Figure illustrates the remote execution loop executed with a JAX-RPC dynamic proxy. The JAX-RPC RMI loop![]() The execution loop in JAX-RPC is the same as any other RMI loop. In Step 1, the client invokes a method on the JAX-RPC proxy that implements the service endpoint interface. The method invocation is transformed into a SOAP message that is sent to the server in Step 2. In Step 3, the web service processes the request and sends the results back as a SOAP response message in Step 4. In Step 5, the SOAP response message is transformed into either a return value or an exception (if it was a SOAP fault) and is then returned to the client. Generating JAX-RPC Artifacts from WSDLThe primary interface that describes a JAX-RPC web service is called a service endpoint interface. A JAX-RPC-compliant compiler generates the endpoint interface from a WSDL <portType> definition. This interface, when combined with WSDL <binding> and <port> definitions, is used to create the dynamic proxy at deploy time. The organization that hosts the web service provides the WSDL document. 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. A WSDL document describes the web service. 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 plain Java interface that extends the java.rmi.Remote interface. All methods on the service endpoint interface must throw RemoteException, although the bean implementation class is not required to. Application exceptions can be thrown from any business method. The interface name, method names, parameters, and exceptions are derived from the WSDL document. Figure shows the mapping between the <portType> and <message> definitions and the endpoint interface. Mapping a WSDL <portType> to a JAX-RPC 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 the <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. Figure shows the mapping between XML Schema built-in types and Java primitives and classes.
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.Double type. In addition, JAX-RPC defines a mapping between complex types defined in the WSDL <types> element and Java bean classes. When a service is deployed, the proxy, which implements the endpoint interface, is generated from the <binding> and <port> definitions. The JAX-RPC proxy translates the messaging style specified by the <binding> definition into a marshaling 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. The proxy is responsible for converting method calls made on the endpoint interface into SOAP messages that are sent to the web service. It's also responsible for converting SOAP response messages sent back to the stub into a return valueor, if it's a SOAP fault message, into an exception thrown by the endpoint method. The proxy also takes into consideration the <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> The address attribute (http://www.charge-it.com/ProcessorService) specifies the URL with which the proxy will exchange SOAP messages. Figure, shown earlier, illustrates how the processor endpoint interface and stub are used to access the Charge-It credit card processing web service. In addition to the service endpoint interface, the JAX-RPC compiler also creates a service interface, which is used to get an instance of the proxy at runtime. The service interface is based on the <service> element of the WSDL document. 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 proxy that is ready to invoke methods on the web service. The getProcessPortAddress( ) method returns the URL that the proxy accesses by default. The getProcessorPort(URL) method allows you to create an endpoint stub that accesses a different URL than the default defined in the WSDL document. Calling a Service from an EJBAs with other resources (JDBC, JMS, and so on), the generated JAX-RPC service can be injected directly into a field or setter method of the bean class. It can also be bound to a specific namespace in the JNDI ENC at deployment time. The service can then obtain the proxy, as described in the previous section. To illustrate how EJBs use a service, 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 Charge-It's Processor web service. We could look up the service directly using JNDI, but injection will save us the extra step. The following code shows the changes to the travelAgentBean class: package com.titan.travelagent; import com.charge_it.Processor; import com.charge_it.ProcessorService; ... @Stateful public class TravelAgentBean implements TravelAgentRemote { @PersistenceContext(unitName="titanDB") private EntityManager em; @PersistenceContext EntityManager em; Customer customer; Cruise cruise; private Cabin cabin; private ProcessorService processorService; ... public TicketDO bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { if (customer == null || cruise == null || cabin == null) { throw new IncompleteConversationalState( ); } try { Reservation reservation = new Reservation( customer, cruise, cabin, price, new Date( )); em.persist(reservation); String customerName = customer.getFirstName( )+" "+ customer.getLastName( ); java.util.Calendar expDate = new Calendar(card.date); Processor processor = processorService.getProcessorPort( ); processor.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); } } ... } ProcessorService is injected directly into the processorService field on the bean class. From this field, we can obtain the proxy object that implements the service endpoint interface. You will see in the next section how to configure an XML deployment descriptor to populate the field. Invoking this proxy within a transactional business process such as bookPassage( ) presents a few problems of which you should be aware. If the proxy encounters a networking problem or SOAP processing error, it throws a RemoteException , 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 is rolled back, but the charge made using the Charge-It web service is not. Invocations on web services do not participate in the caller's transaction context! The <service-ref> Deployment ElementWe still need to specify where and how this web service reference is defined. To do this we need a <service-ref> element in our EJB deployment descriptor. This XML element binds a JAX-RPC service to the JNDI ENC and injects it into the processorService field of our bean class. EJB XML is allowed to be a partial deployment descriptor. This means that we do not have to define every single piece of metadata in XML just because we want to inject a web service into our bean class. The partial XML deployment descriptor of the 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_3_0.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> <mapped-name>webservices/ProcessorService</mapped-name> <injection-target> <injection-target-class> com.titan.travelagent.TravelAgentBean </injection-target-class> <injection-target-name>processorService </ injection-target-name> </injection-target> </service-ref> </session> </enterprise-beans> </ejb-jar> The <service-ref-name> element declares the name of the JAX-RPC service in the JNDI ENCit's always relative to the java:comp/env context. The <service-interface> element 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, whether you are using a <service-ref> element or an annotation to inject your web service. 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> element 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 to understand the mapping between the WSDL document and the service endpoint interfaces.) The <service-qname> element 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. The <mapped-named> element is a vendor-specific identifier that can map to a global registry of the application server (usually the global JNDI). The <injection-target> element is used to tell the EJB container to inject an instance of ProcessorService into the processorService field of the TRavelAgentBean class. You can find more information about using <injection-target> in Chapter 14. The JAX-RPC Mapping FileA JAX-RPC service or client in a Java EE environment must have a JAX-RPC mapping file. The mapping file format is defined in the "Implementing Enterprise Web Services" (JSR-109) specification. There are many possible ways to map or bind Java to WSDL, and WSDL to Java. This can cause portability problems between different JAX-RPC implementations, since they may bind Java and WSDL in different ways. The JAX-RPC mapping file addresses this problem by configuring the specific details for how binding is supposed to occur. This allows for a service and a client to be portable across different Java EE implementations. The mapping file for the ChargeItProcessor.wsdl document follows:
<?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.Calendar</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>
As you can see, 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. The complete JAX-RPC mapping file is too complicated to discuss in detail. Don't worry, though, you normally don't write these by hand. A Java EE implementation will provide tools to generate these files for you. Also, the JAX-RPC mapping file is no longer used in JAX-WS. We will discuss how it handles binding in the section "Using JAX-WS," later in this chapter. |
- Comment

