An EJB endpoint is a stateless session bean that serves as a web service. Basically, the endpoint exposes a stateless session bean through a new component interface, called the endpoint interface; remote clients use SOAP 1.1 to access the methods defined in this interface. Because an EJB endpoint is simply a SOAP-accessible stateless session bean, it has the same advantages as other EJBs. An EJB endpoint runs in the EJB container that automatically manages transactions and security and provides access to other EJBs and resources via the JNDI ENC.
To illustrate how an EJB endpoint is developed, we’ll create a new version of the TravelAgent EJB. The revised TravelAgent will use the same logic as the TravelAgent EJB developed in Chapter 11 and the ReservationProcessor developed in Chapter 12, but it will be deployed as a stateless session bean with an endpoint interface. The TravelAgent endpoint is based on the WSDL document shown earlier in this chapter.
Every EJB endpoint must have a WSDL document
that describes the web service. The
<portType>
declared by the WSDL document
must be aligned with the endpoint interface of the web service. In
other words, the mapping between the WSDL
<portType>
and the endpoint interface must
be correct according to the JAX-RPC specification. One way to
accomplish this is to create the WSDL document first, and then use it
to generate the endpoint interface:
<?xml version="1.0"?> <definitions name="TravelAgent" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"xmlns:titan="http://www.titan.com/TravelAgent"
targetNamespace="http://www.titan.com/TravelAgent"><!-- message elements describe the parameters and return values -->
<message name="RequestMessage"> <part name="cruiseId" type="xsd:int" /> <part name="cabinId" type="xsd:int" /> <part name="customerId" type="xsd:int" /> <part name="price" type="xsd:double" /> </message> <message name="ResponseMessage"> <part name="reservationId" type="xsd:string" /> </message><!-- portType element describes the abstract interface of a web service -->
<portType name="TravelAgentEndpoint"> <operation name="makeReservation"> <input message="titan:RequestMessage"/> <output message="titan:ResponseMessage"/> </operation> </portType><!-- binding element tells us which protocols and encoding styles are used -->
<binding name="TravelAgentBinding" type="titan:TravelAgentEndpoint"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/> <operation name="makeReservation"> <soap:operation soapAction="" /> <input> <soap:body use="literal" namespace="http://www.titan.com/TravelAgent"/> </input> <output> <soap:body use="literal" namespace="http://www.titan.com/TravelAgent"/> </output> </operation> </binding><!-- service element tells us the Internet address of a web service -->
<service name="TravelAgentService"> <port name="TravelAgentPort" binding="titan:TravelAgentBinding"> <soap:address location="http://www.titan.com/webservices/TravelAgent" /> </port> </service> </definitions>
Based on this WSDL document, we can generate a
JAX-RPC endpoint interface, which
will be implemented by our EJB endpoint. The endpoint interface is
generated from the <portType>
and
<message>
definitions (and
<types>
, if present). The endpoint interface
looks like this:
package com.titan.webservice;
public interface TravelAgentEndpoint
extends java.rmi.Remote {
public java.lang.String makeReservation(int cruiseId, int cabinId,
int customerId, double price)
throws java.rmi.RemoteException;
}
The endpoint interface defines the business methods that will be
accessible as SOAP operations. The interface extends
java.rmi.Remote
—there is no EJBObject
interface—and defines one or more business methods, each of
which must throw a java.rmi.RemoteException
. The
types that can be used as parameters and return types are the same
types that can be used with JAX-RPC generated endpoints (see Table 15-1). You can also use simple Java bean types for
holding complex data.
An EJB endpoint does not define a home interface; there is no EJB home object for creating or locating EJB endpoints. An EJB endpoint cannot be created or located; it’s a truly stateless service, both semantically and physically. The only time an EJB would have a home interface is if the EJB defined remote or local interfaces in addition to the endpoint interface. In other words, a single EJB can be local, remote, and an endpoint.
The bean class defined for the TravelAgent endpoint must implement
the methods defined by the endpoint interface. A
stateless bean class can implement
the endpoint interface directly—something
that’s not recommended for the local or remote
interfaces. That’s because the endpoint interface is
a direct descendent of java.rmi.Remote
, and
doesn’t define any EJBObject
methods. Here’s the new definition for the
TravelAgent bean class:
package com.titan.webservice; import com.titan.reservation.*; import com.titan.cruise.*; import com.titan.customer.*; import com.titan.cabin.*; import com.titan.processpayment.*; import java.rmi.RemoteException; import javax.rmi.PortableRemoteObject; import javax.naming.NamingException; import javax.ejb.EJBException; import java.util.Date; import java.util.Calendar; public classTravelAgentBean
implements TravelAgentEndpoint, javax.ejb.SessionBean
{ public javax.naming.Context jndiContext; public void ejbCreate( ) {} public String makeReservation(int cruiseId, int cabinId, int customerId, double price){ try { CruiseLocal cruise = this.getCruise(cruiseId); CabinLocal cabin = this.getCabin(cabinId); CustomerRemote customer = this.getCustomer(customerId); CreditCardDO card = this.getCreditCard(customerId); ReservationHomeLocal resHome = (ReservationHomeLocal) jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal"); ReservationLocal reservation = resHome.create(customer, cruise, cabin, price, new Date( )); Object ref = jndiContext.lookup( "java:comp/env/ejb/ProcessPaymentHomeRemote"); ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class); ProcessPaymentRemote process = ppHome.create( ); process.byCredit(customer, card, price); return reservation.getPrimaryKey( ).toString( ); } catch(Exception e) { throw new EJBException(e); } } public CustomerRemote getCustomer(int customer_id) throws Exception { Integer customerID = new Integer(customer_id); CustomerHomeRemote home = (CustomerHomeRemote) jndiContext.lookup("java:comp/env/ejb/CustomerHomeRemote"); return home.findByPrimaryKey(customerID); } public CreditCardDO getCreditCard(int customer_id) throws Exception{ Integer customerID = new Integer(customer_id); CustomerHomeLocal home = (CustomerHomeLocal) jndiContext.lookup("java:comp/env/ejb/CustomerHomeLocal"); CustomerLocal customer = home.findByPrimaryKey(customerID); CreditCardLocal card = customer.getCreditCard( ); return new CreditCardDO(card.getNumber( ),card.getExpirationDate( ), card.getCreditOrganization( )); } public CabinLocal getCabin(int cabin_id) throws Exception { Integer cabinID = new Integer(cabin_id); CabinHomeLocal home = (CabinHomeLocal) jndiContext.lookup("java:comp/env/ejb/CabinHomeLocal"); return home.findByPrimaryKey(cabinID); } public CruiseLocal getCruise(int cruise_id) throws Exception { Integer cruiseID = new Integer(cruise_id); CruiseHomeLocal home = (CruiseHomeLocal) jndiContext.lookup("java:comp/env/ejb/CruiseHomeLocal"); return home.findByPrimaryKey(cruiseID); } public void ejbRemove( ) {} public void ejbActivate( ) {} public void ejbPassivate( ) {} public void setSessionContext(javax.ejb.SessionContext cntx){ try { jndiContext = new javax.naming.InitialContext( ); }catch(NamingException ne) { throw new EJBException(ne); } } }
The TravelAgentBean
class is not that different
from the TravelAgent EJB developed earlier in this chapter (the
version that uses the Charge-It credit card processing web service).
The primary difference is that it responds to web service calls,
rather than remote or local calls.
The TravelAgent endpoint requires four deployment files: a standard ejb-jar.xml deployment descriptor, a WSDL file, a JAX-RPC mapping file, and a webservices.xml file.
An EJB endpoint is deployed using the same
ejb-jar.xml
elements as a regular stateless
session bean. The endpoint declares a single component interface
element, the <service-endpoint>
. This
element can be used only with stateless session beans that are
deployed as EJB endpoints. A single EJB can actually support remote,
local, and endpoint interfaces simultaneously. Here,
we’ll keep it simple and limit the TravelAgent
endpoint to web services. Other than the
<service-endpoint>
element, the rest of the
deployment descriptor is pretty much the same as a regular stateless
session bean:
<?xml version='1.0' encoding='UTF-8' standalone='yes'?> <ejb-jar 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://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd" version="2.1"> <enterprise-beans> <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> <ejb-ref-name>ejb/ProcessPaymentHomeRemote</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>com.titan.processpayment.ProcessPaymentHomeRemote</home> <remote>com.titan.processpayment.ProcessPaymentRemote</remote> </ejb-ref> ... </session> ... </enterprise-beans> <assembly-descriptor> ... </assembly-descriptor> </ejb-jar>
The value for the <ejb-name>
element can be
anything you choose; in this book, we use the suffix
“Endpoint” to denote an EJB
endpoint component.
You cannot declare the transaction attribute of any method of an endpoint as mandatory , because doing so implies that the Enterprise Bean method must be enrolled in the calling client’s transaction. Since transaction propagation is not standardized in web services, it’s assumed that the client will not be propagating a transaction.
The WSDL file used to generate the endpoint interface must be packaged with the EJB endpoint. Normally, the WSDL document is placed in the META-INF directory of the JAR file, but it can go anywhere as long as it’s in the same JAR file as the EJB endpoint.
EJB endpoints, like JAX-RPC generated stubs, require you to define a JAX-RPC mapping file. The mapping file can have any name, but it should be descriptive, and the file type should be XML. It’s common to name this file mapping.xml or travelagent_mapping.xml, or something along those lines. Here’s a lightweight JAX-RPC mapping file for the TravelAgent endpoint:
<?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.titan.webservice</package-type>
<namespaceURI>http://www.titan.com/TravelAgent</namespaceURI>
</package-mapping> </java-wsdl-mapping>
The JAX-RPC mapping file was covered earlier in this chapter, in the
section entitled “The JAX-RPC Mapping
File.” Basically, this deployment descriptor maps a
Java package to the XML Namespace of the WSDL
<port>
and other elements, helping the
container to understand which packaged classes are associated with
which WSDL definitions.
The webservices.xml file is the baling wire that ties the separate deployment files together. It defines the relationships between the ejb-jar.xml, the WSDL file, and the JAX-RPC mapping file:
<?xml version='1.0' encoding='UTF-8' ?> <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:TravelAgentPort</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>
The <webservice-description>
element
describes an EJB endpoint: there may be one or more of these elements
in a single webservices.xml file.[46] The
<webservice-description-name>
is a unique
name assigned to the web service description. It can be anything you
like. The <wsdl-file>
element points to the
WSDL document of the EJB endpoint. Each EJB endpoint has exactly one
WSDL document, which is usually located in the
META-INF directory of the EJB-JAR file. When the
EJB endpoint is deployed, your deployment tool will probably provide
you with the option of copying the WSDL document to some type of
public URL or registry so that others can discover the web service.
The <jaxrpc-mapping-file>
element indicates
the location of the JAX-RPC mapping file that is associated with the
EJB endpoint and the WSDL document. It, too, is usually located in
the META-INF directory of the EJB JAR file.
The <port-component>
element maps a
stateless session bean declared in the
ejb-jar.xml file to a specific
<port>
in the WSDL document. The
<port-component-name>
is the logical name
you assign the EJB endpoint. It can be anything. The
<wsdl-port>
element maps the EJB endpoint
deployment information to a specific WSDL
<port>
element in the WSDL document. The
<service-endpoint-interface>
is the fully
qualified name of the endpoint interface—it must be the same
interface declared by the <service-endpoint>
element for the EJB in the ejb-jar.xml file. The
<service-impl-bean>
and its
<ejb-link>
element link the
<port-component>
to a specific EJB in the
ejb-jar.xml. The value of the
<ejb-link>
must match the value of the
<ejb-name>
in the
ejb-jar.xml file.
[46] The <webservice-description>
element
can also describe a JAX-RPC service endpoint, which is a
servlet-based web service that is outside the scope of this
book.