In this exercise, you will learn how to use JAX-RPC’s client and server-side programming model with EJB 2.1. You will expose a stateless session bean as a web service. You will also investigate how to connect to and invoke on an existing web service from within EJB code. The stateless session bean that is exposed models the TravelAgentEndpoint in Chapter 15 of the EJB book. The supporting code for the rest of this exercise is borrowed from the exercises for Chapter 11 (Workbook 8). This exercise also introduces another stateless session bean that acts as a JAX-RPC client to the TravelAgentEndpoint EJB.
Perform the following steps:
Open a command prompt or shell terminal and change to the
ex15_1
directory created by the extraction
process.
Set the JAVA_HOME
and
JBOSS_HOME
environment variables to point to where
your JDK and JBoss 4.0 are installed. Examples:
Windows:C:workbookex15_1> set JAVA_HOME=C:jdk1.4.2 C:workbookex15_1> set JBOSS_HOME=C:jboss-4.0
|
Unix:$ export JAVA_HOME=/usr/local/jdk1.4.2 $ export JBOSS_HOME=/usr/local/jboss-4.0
|
Add ant
to your execution path. Ant is the build
utility.
Windows:C:workbookex15_1> set PATH=..antin;%PATH%
|
Unix:$ export PATH=../ant/bin:$PATH
|
You need to clean and refresh the database. To do this, first
shutdown JBoss if you have it running and then run the
ant
clean.db
.
JBoss implements web services integration using the
Apache
Axis project http://ws.apache.org/axis/. One of the more
annoying things about web services and EJB is creating a WSDL
document based on a Service Endpoint interface. To alleviate this
work, Axis has a nice tool called Java2WSDL that allows you to
automatically generate a WSDL document based on a plain Java
interface. If you examine the build.xml
file,
you can see an ant target devoted to invoking this utility.
<target name="wsdl" depends="compile"> <java classname="org.apache.axis.wsdl.Java2WSDL" fork="yes" dir="."> <classpath refid="classpath"/> <arg value="-lhttp://localhost:8080/ws4ee/services/TravelAgentService"/> <arg value="-uLITERAL"/> <arg value="-sTravelAgentEndpoint"/> <arg value="-o${src.resources}/META-INF/travelagent.wsdl"/> <arg value="com.titan.webservice.TravelAgentEndpoint"/> </java> <copy file="${src.resources}/META-INF/travelagent.wsdl" todir="${src.resources}/client/META-INF/" /> </target>
The -l
switch tells Java2WSDL the default service
location URL that will be used by a client connection. The
-uLITERAL
switch tells Axis to generate WSDL with
RPC/Literal messaging. No one takes the default RPC/Encoded messaging
seriously anymore as it doesn’t interoperate very
well. The -o
switch just specifies where the WSDL
file should be generated. The class name of the Service Endpoint
Interface (it can be any Java interface) must be specified as an
argument and must also be within the classpath.
In this exercise there are two EJB jar files.
One is titan.jar, which contains
TravelAgentEndpoint and other supporting EJBs; the other is
titan-client.jar, which contains the EJB that
will be connecting to TravelAgentEndpoint as a JAX-RPC client. Both
of these jars require the
travelagent.wsdl
file to do their things.
To do the build, perform the following steps:
$ ant ejbjar
You will see titan.jar
and
titan-client.jar
built, copied to the JBoss
deploy
directory, and redeployed by the
application server.
So where’s the JAX-RPC stub generation? The spirit of JBoss has always been to avoid any precompilation step. If you have run any of the other examples in this book, you will have seen that there is not any stub generation for EJBs either. At deployment time, JBoss automatically generates dynamic proxies to handle all web service communication both with clients and services.
To illustrate how to expose a stateless session bean, the
TravelAgentEJB from Exercise 4.2 has been extended. This first thing
to be done was to define a Service Endpoint interface the web service
will implement. This interface is defined in
src/main/com/titan/travelagent.
package com.titan.webservice; public interface TravelAgentEndpoint extends java.rmi.Remote { String makeReservation(int cruiseId, int cabinId, int customerId, double price) throws java.rmi.RemoteException; }
This interface is taken directly from Chapter 15
of the EJB book. Next, you have to define all the deployment
descriptors. These files reside in
src/resources/META-INF
.
<session> <description> A Web Service reservation service </description> <ejb-name>TravelAgentEjbEndpoint</ejb-name> <service-endpoint> com.titan.webservice.TravelAgentEndpoint </service-endpoint> <ejb-class> com.titan.webservice.TravelAgentBean </ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> <ejb-ref> ...
This XML is taken directly from Chapter 15 of the EJB book and added to the definition of the other supporting EJBs.
<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.titan.webservice</package-type> <namespaceURI> http://webservice.titan.com/TravelAgentEndpoint </namespaceURI> </package-mapping> </java-wsdl-mapping>
The endpoint we are exposing follows the guidelines for a simple
mapping file. The namespaceURI
element is a little
different from Chapter 15 of the EJB book
because it should match the generated WSDL document.
<webservices xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:titan="http://www.titan.com/TravelAgent" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_web_services_1_1.xsd" version="1.1"> <webservice-description> <webservice-description-name> TravelAgentService </webservice-description-name> <wsdl-file>META-INF/travelagent.wsdl</wsdl-file> <jaxrpc-mapping-file> META-INF/travelagent_mapping.xml </jaxrpc-mapping-file> <port-component> <port-component-name>TravelAgentEndpoint</port-component-name> <wsdl-port>titan:TravelAgentEndpoint</wsdl-port> <service-endpoint-interface> com.titan.webservice.TravelAgentEndpoint </service-endpoint-interface> <service-impl-bean> <ejb-link>TravelAgentEjbEndpoint</ejb-link> </service-impl-bean> </port-component> </webservice-description> </webservices>
This is a standard webservices.xml descriptor.
It links the WSDL file, mapping file, Service Endpoint interface, and
TravelAgentEndpoint EJB all together. The important part of this file
as it pertains to Jboss is the
<webservice-description-name>
. JBoss binds
all deployed web services under the
/ws4ee/services/<webservice-description-name>
URL. For this example, it would be under
/ws4ee/services/TravelAgentEndpoint. You can
also view all endpoints by going to the base URL
/ws4ee/services (Figure 31-1).
TravelAgentClientEJB is a stateless session bean that illustrates how to invoke a web service from within EJB code. It simply exposes the same interface as TravelAgentEndpoint EJB and implements it by delegating to the TravelAgentEndpoint interface, invoking over the wire via a SOAP invocation.
public String makeReservation(int cruiseId, int cabinId, int customerId, double price) throws java.rmi.RemoteException { try { javax.naming.Context jndiContext = new InitialContext( ); Object obj = jndiContext.lookup("java:comp/env/service/TravelAgent"); javax.xml.rpc.Service svc = (javax.xml.rpc.Service) obj; TravelAgentEndpoint endpoint = (TravelAgentEndpoint) svc.getPort(TravelAgentEndpoint.class); return endpoint.makeReservation(cruiseId, cabinId, customerId, price); } catch (Exception e) { e.printStackTrace( ); throw new EJBException("failed"); } }
Since the spirit of JBoss is to avoid stub generation, the preferred
method for clients is the Dynamic Proxy API as JBoss will
automatically set up all proxies at deploy time.
TravelAgentClientBean.makeReservation
is an
example of this. A generic proxy is registered for the service
reference and you can get a proxy to any endpoint interface you want
by passing in a Java Class parameter to the
getPort
method.
<session> <ejb-name>TravelAgentClientEJB</ejb-name> ... <service-ref> <service-ref-name>service/TravelAgent</service-ref-name> <service-interface> javax.xml.rpc.Service </service-interface> <wsdl-file>META-INF/travelagent.wsdl</wsdl-file> <jaxrpc-mapping-file> META-INF/travelagent_mapping.xml </jaxrpc-mapping-file> </service-ref> </session>
The
<service-ref>
element is standard. One thing to note
is that the
<service-qname>
element can be left out if the WSDL
file contains only one service definition. The other deployment
descriptors are the same descriptors as in the server model.
<wsdl:service name="TravelAgentEndpointService"> <wsdl:port name="TravelAgentEndpoint" binding="impl: TravelAgentEndpointSoapBinding"> <wsdlsoap:address location="http://localhost:8080/ws4ee/services/ TravelAgentService"/> </wsdl:port> </wsdl:service>
The address location in the TravelAgentEJB.wsdl file is the URL used by the Dynamic Proxy created in the listCabins method.
The client application is made up of two clients. The first client initializes the entity beans and database tables that are needed for this exercise.
C:workbookex15_1>ant createdb Buildfile: build.xml prepare: compile: createdb: [java] Calling TravelAgentBean to create sample data.. [java] All customers have been removed [java] All cabins have been removed [java] All ships have been removed [java] All cruises have been removed [java] All reservations have been removed [java] Customer with ID 1 created (Burke Bill) [java] added credit card: 4300000000000000 for Bill [java] Customer with ID 2 created (Labourey Sacha) [java] Created ship with ID 101... [java] Created ship with ID 102... [java] Created cabins on Ship A with IDs 100-109 [java] Created cabins on Ship B with IDs 200-209 [java] Created Alaska Cruise with ID 0 on ShipA [java] Created Norwegian Fjords Cruise with ID 1 on ShipA [java] Created Bermuda or Bust Cruise with ID 2 on ShipA [java] Created Indian Sea Cruise with ID 3 on ShipB [java] Created Australian Highlights Cruise with ID 4 on ShipB [java] Created Three-Hour Cruise with ID 5 on ShipB [java] Made reservation for Customer 1 on Cruise 0 for Cabin 103 [java] Made reservation for Customer 1 on Cruise 5 for Cabin 208 [java] Made reservation for Customer 2 on Cruise 1 for Cabin 105 [java] Made reservation for Customer 2 on Cruise 5 for Cabin 202 [java] Creating database table...
The second client is a MakeReservation script that can be run from
the command line. There is a script provided for both Unix and
Windows. The arguments for the script are a cruise ID, cabin ID, a
customer ID, and finally a price for the reservation. You can pull
three of the arguments from the output of
run.createdb
:
C:workbookex15_1>MakeReservation 1 106 1 5000.00 Buildfile: build.xml prepare: compile: ejbjar: run.client: [java] reservation 5 completed.
Here we are, back at the dock, our “EJB on JBoss” cruise complete! We really hope you’ve enjoyed the voyage and that we’ll soon meet you on JBoss’s forums for some more exciting adventures.