Enterprise JavaBeans Web Services





Enterprise JavaBeans Web Services

In J2EE 1.4 you can deploy a stateless session bean as a Web service endpoint that can process SOAP messages as RPC calls. Making a stateless session bean accessible as a Web service involves basically the same process used to deploy a stateless bean with a remote or local interface. The only big difference is that you define an endpoint interface that extends javax.rmi.Remote, instead of a remote interface that extends javax.ejb.EJBObject, or a local interface that extends javax.ejb.EJBLocalObject. In addition, you do not define a home interface, because an EJB endpoint does not have one.

An EJB endpoint can be a brand-new stateless session bean developed specifically to serve as a Web service endpoint, or you can re-deploy an existing stateless session bean so that it doubles as an endpoint. As long as you observe the restrictions on endpoint interfaces imposed by the JAX-RPC specification, you can turn a stateless session bean into a Web service endpoint with very little effort. In fact, a single stateless session bean can conceivably service remote, local, and endpoint clients simultaneously.

1 A Simple Example

The easiest way to show you how to deploy a stateless session bean as a Web service endpoint is to use an example. In this case we'll deploy the BookQuote Web service as an EJB endpoint. In reality, BookQuote is so simple it's best deployed as a JSE; it does not need the complex transaction management capabilities provided by Enterprise JavaBeans. It's an example that should be familiar to you by now, though, so using it at this point will allow you to focus on the mechanics of defining an EJB endpoint.

1.1 The Endpoint Interface

EJB developers usually start by developing a component's interface, which defines the business methods that clients can invoke on the EJB at runtime. If we wanted the EJB to be accessible via Java RMI-IIOP, we would create a remote interface that extends javax.ejb.EJBObject. If we wanted the EJB to be accessible to co-located beans (other components deployed in the same J2EE application), we would use a local interface that extends javax.ejb.EJBLocalObject. In this case, however, we want the EJB to be accessible to SOAP clients, so we'll define an endpoint interface that extends the java.rmi.Remote interface. The endpoint interface defines the Web service operations that SOAP clients can call on the EJB. As shown in Listing 11-6, the BookQuote Web service declares a single operation, defined by the getBookPrice() method.

-6 An Endpoint Interface
package com.jwsbook.jaxrpc;

public interface BookQuote extends java.rmi.Remote {
  public float getBookPrice(String isbn) throws java.rmi.RemoteException;
}

The endpoint interface defined here is identical to the one defined for the JSE in Chapter 10. That they're exactly the same is telling: It illustrates that from the SOAP client's perspective there is no difference between a JSE and an EJB endpoint Web service.

The endpoint interface must comply with the rules for WSDL-to-Java mapping defined by the JAX-RPC specification. These rules are not all that complicated as long as you stick with primitive types. Classes and arrays are also supported, but they require a little more work. Complete coverage of WSDL-to-Java mapping is given in Chapter 15.

Every business method defined in the endpoint interface must declare the java.rmi.RemoteException in its throws clause. RemoteException is used to report any networking problems associated with processing a client's SOAP request. SOAP clients will not use the EJB endpoint interface directly. Instead SOAP clients will use the WSDL document associated with the EJB endpoint to generate their own service interface. If the client is a Java client, then you will probably use the JAX-RPC compiler to generate the client's own endpoint interface and stubs as I explained in Section 12.1: Generated Stubs. If the client is a .NET application or a Perl program or some other kind of client, you'll use some other SOAP toolkit to generate appropriate service interfaces to the Web service. The real purpose of the endpoint interface is to declare the Web service operations supported by the EJB endpoint, and any faults those operations may generate.

At deployment time the J2EE application server's deployment tools can examine the endpoint interface, along with other information provided by deployment descriptors, and use it to generate the portType and message definitions of the WSDL document. For example, the BookQuote endpoint interface maps to the definitions in Listing 11-7.

Listing 11-7 The WSDL Generated from the BookQuote Interface
<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:tns="http://www.Monson-Haefel.com/ed1/BookQuote"
  targetNamespace="http://www.Monson-Haefel.com/ed1/BookQuote" ...>
  <message name="getBookPriceRequest">
    <part name="isbn" type="xsd:string"/>
  </message>
  <message name="getBookPriceResponse">
    <part name="result" type="xsd:float"/>
  </message>
  <portType name="BookQuote">
    <operation name="getBookPrice">
      <input message="tns:getBookPriceRequest"/>
      <output message="tns:getBookPriceResponse"/>
    </operation>
  </portType>
  ...
</definitions>
1.2 The Bean Class

Once you've defined the endpoint interface, you can define the bean class, which will do the actual work of processing BookQuote Web service requests. Listing 11-8 defines the BookQuoteBean class.

-8 The EJB Endpoint Bean Class
package com.jwsbook.jaxrpc;
import javax.ejb.SessionContext;

public class BookQuoteBean_1 implements javax.ejb.SessionBean {
  public void ejbCreate( ){}

  public float getBookPrice(String isbn) {
    return 24.99f;
  }

  public void setSessionContext(SessionContext cntxt){}
  public void ejbActivate() {}
  public void ejbPassivate() {}
  public void ejbRemove(){}
}

Although the bean class must implement all methods defined in the endpoint interface, it's not required to implement the interface itself. This is in keeping with the EJB platform convention of not implementing remote or local interfaces. It doesn't hurt anything to make the bean class implement the endpoint interface, though, because unlike the remote and local interfaces, the endpoint interface defines only business methods—it doesn't inherit EJB object methods. In any case, whether you choose to extend the endpoint interface explicitly or not, the bean class must implement methods that match the endpoint interface methods exactly. The only exception to this rule is the Exception types thrown by the bean class's methods. Each method in the implementation class must throw the same exceptions that the corresponding methods in the interface throw—except one: java.rmi.RemoteException. The endpoint bean's methods must never declare RemoteException.

A SOAP client will use the WSDL document associated with the EJB endpoint to send an appropriate SOAP message to the J2EE application server that hosts the EJB endpoint. Figure shows how an EJB endpoint processes a SOAP message sent from a Web service client.

An EJB Endpoint Handling a SOAP Call

graphics/11fig01.gif

Invoking an EJB Web service as illustrated in Figure proceeds as follows:

  1. The Web service client sends a SOAP message to the EJB container hosting the EJB endpoint.

  2. When the EJB container receives the SOAP message, it extracts the parameters from the message, then invokes the corresponding method of an EJB instance.

  3. The EJB endpoint processes the request. If there is a return value, the EJB container constructs a SOAP reply message. If the endpoint method throws an exception, the EJB container generates an appropriate SOAP fault message.

  4. The EJB container sends the SOAP reply or fault message back to the Web service client.

Every stateless session bean, whether it's used as a Web service endpoint or not, must define a bean class that implements the javax.ejb.SessionBean interface. The SessionBean interface defines a number of callback methods the container uses to interact with the bean. The purpose of these callback methods is discussed in detail later, in Section 11.2.2.2.

In addition to the callback methods, an EJB endpoint's bean class must declare an ejbCreate() method. This may perform initialization work that cannot be done in the setSessionContext() method. Specifically, it can access the EJB object, EJB home, and TimerService objects that are not available in the setSessionContext() method.

The ejbCreate() method must not declare any parameters because it's called by the container rather than a client, so there are no arguments to pass to it. If you find the purpose of the ejbCreate() method in stateless session beans a bit ambiguous, be content—you are not alone. The reason it's there has to do with providing a programming model that is consistent with other bean types, especially the stateful session bean, whose ejbCreate() method is actually directly linked to calls made on its EJB home. Because EJB endpoints do not have a home interface, the role of the ejbCreate() method is further marginalized. You must define it, however, if you want your EJB to deploy, so simply accept it as "one of those things" and implement it with an empty method body.

1.3 Deployment Descriptors and Packaging

In addition to the endpoint interface and the bean class, the EJB developer must provide a deployment descriptor, and will usually need to package the EJB endpoint in a JAR file. In many cases a J2EE deployment tool will do this automatically. All you'll need to do is fill in fields related to security and transactions and then choose some deployment option from the GUI. It's always helpful, however, to have some idea of what is in the deployment descriptor because it helps you understand the information used by the container—and you may have to write the deployment descriptor by hand if your J2EE deployment tool doesn't do it for you.

The deployment descriptors and packaging are discussed in more detail in Part VII. The whole deployment process is a bit complicated. Fundamentally, there are at least two basic deployment descriptors you must create for an EJB endpoint:

The ejb-jar.xml deployment descriptor declares the bean class, the local and remote interfaces, and the endpoint interface. In addition, it declares the JNDI ENC references to other beans, resources, Web services, and environment variables. Finally, it declares the security and transaction attributes of the EJB.

The webservices.xml deployment descriptor describes each Web service endpoint and links these descriptions to an endpoint, either JSE or EJB. In either case webservices.xml declares the location of the WSDL document, the port definition associated with the endpoint, the JAX-RPC mapping file, the endpoint interface, and a link to the actual component.

In addition to ejb-jar.xml and webservices.xml, the deployment tool will generate a JAX-RPC mapping file, which defines more complex mappings, between Java objects used as parameters and return values on one hand, and the XML types used in SOAP and WSDL on the other.

The deployment descriptors, endpoint interface, user-defined types, and bean class are all packaged together in a JAR file according to the packaging conventions described in the EJB and Web Services for J2EE 1.1 specification. Again, all this is covered in more detail in Part VII.

2 The EJB Runtime Environment

At runtime an EJB can access resources, other EJBs, and Web services using its JNDI ENC. (If you're not familiar with the JNDI Environment Naming Context, you should read Section 10.2.2 before proceeding into this section.) In addition, an EJB can interact with its container via callback methods and the SessionContext interface. Transactions and security are managed automatically by the EJB container system, which is responsible for starting and ending new transactions, propagating client transactions, authenticating and authorizing clients, and propagating both transactions and security identities from one EJB to the next. The following sections provide a detailed explanation of the EJB's interface to the container, as well as a detailed discussion of transaction and security attributes.

2.1 The JNDI Environment Naming Context

We can beef up the BookQuote bean class by using the JNDI ENC to obtain a JDBC connection, which can then be used to fetch the wholesale price of a book from a database. Listing 11-9 illustrates.

-9 An EJB Endpoint That Uses JDBC
package com.jwsbook.jaxrpc;
import javax.ejb.SessionContext;

public class BookQuoteBean_2 implements javax.ejb.SessionBean {
  SessionContext ejbContext;
  public void ejbCreate( ){}

  public float getBookPrice(String isbn){
    java.sql.Connection jdbcConnection = null;
    java.sql.Statement sqlStatement = null;
    java.sql.ResultSet resultSet;
    try {
      javax.naming.InitialContext jndiEnc =
                               new javax.naming.InitialContext();
      javax.sql.DataSource dataSource = (javax.sql.DataSource)
                  jndiEnc.lookup("java:comp/env/jdbc/DataSource");
      jdbcConnection = dataSource.getConnection();
      sqlStatement = jdbcConnection.createStatement();
      resultSet = sqlStatement.executeQuery(
        "SELECT wholesale FROM CATALOG WHERE isbn = \'"+isbn+"\'");

      if(resultSet.next()){
        float price = resultSet.getFloat("wholesale");
        return price;
      }
      return 0;// zero means its not stocked.
    }catch (java.sql.SQLException se) {
      throw new RuntimeException("JDBC access failed");
    }catch (javax.naming.NamingException ne){
      throw new RuntimeException("JNDI ENC access failed");
    }
  }
  ...
}

Obviously, this implementation is far more useful than simply returning a literal value. Wholesale prices are actually obtained from a relational database, which can provide up-to-date wholesale pricing on any book in Monson-Haefel Books' catalog. The JNDI ENC used by EJB endpoints is pretty much the same as the JNDI ENC used by the JSE. For a more detailed explanation of the JNDI ENC, see Section 10.2.2.

11.2.2.2 The SessionBean Interface

A stateless session bean class must implement the javax.ejb.SessionBean interface. The container uses this interface to alert the bean to events in its life cycle. Its definition is shown in Listing 11-10.

Listing 11-10 The javax.ejb.SessionBean Interface
package javax.ejb;
public interface SessionBean extends javax.ejb.EnterpriseBean {
    public void setSessionContext(SessionContext cntxt);
    public void ejbActivate();
    public void ejbPassivate();
    public void ejbRemove();
}

A stateless session bean's life cycle is really very simple. Initially the EJB container system might create a few stateless session beans for each deployment, and keep them in an instance pool, ready to handle requests. A specified procedure is followed while creating new instances and preparing them to service requests as illustrated in Figure.

Stateless Session Bean Life Cycle

graphics/11fig02.gif

Creation of each bean instance entails three method calls, in this order:

  1. The EJB container instantiates the bean class by calling its newInstance() method (this is equivalent to using a no-arg constructor).

  2. The container then calls the bean's setSessionContext() method—just this once in the life of the bean instance.

  3. At some point before the bean instance handles its first client request, the container calls its ejbCreate() method—again, only this one time.

After these three methods have been called, the bean instance enters the method-ready pool, at which point it is all set to process client requests. As the EJB container receives SOAP messages, it matches them up with the appropriate EJB, then selects an instance of that EJB from the method-ready pool. The SOAP call is delegated to the corresponding method of the instance. After the method returns, the bean instance is returned to the instance pool, ready for another request.

A stateless instance must not maintain client-specific state information between method invocations because, with every request, it may be serving a completely different client. It's for this reason that a stateless session bean is stateless.

Eventually the instance life cycle will come to an end, typically for one of three reasons: The J2EE application server is shut down, the EJB container reduces the number of instances in the pool to conserve resources, or an unexpected failure causes the J2EE application server to go down. In the first two cases, where the instance life cycle ends naturally, the EJB container will call the bean's ejbRemove() method to notify the instance that it's about to be dereferenced and made eligible for garbage collection. At that point, the bean instance should close any resources it has opened. In the event of an unanticipated shutdown, the bean instance is simply lost when the process that is running the EJB container's VM ends.

Although the ejbActivate() and ejbPassivate() callback methods are useful to stateful session beans (which also implement the SessionBean interface), they are not used with stateless session beans. Because they are part of the Session Bean interface, the stateless bean class must implement them, so just provide an empty implementation for each.

11.2.2.3 The SessionContext

The SessionContext provides the EJB endpoint instance with some information and access to functionality that may be useful while serving SOAP clients. The EJB instance receives the SessionContext when its setSessionContext() method is called. Because this method is called only once in the life of an instance, it must store the reference to the SessionContext in an instance variable if it wants to use it later, as in this snippet from Listing 11-9:

package com.jwsbook.jaxrpc;
import javax.ejb.SessionContext;

public class BookQuoteBean_2 implements javax.ejb.SessionBean {
  SessionContext ejbContext;
  ...
  public void setSessionContext(SessionContext cntxt){
    ejbContext = cntxt;
  }
  ...
}

The SessionContext is an interface implemented by the container. The information and functionality provided by SessionContext changes depending on the how the EJB is deployed, what SOAP client is being served, and from which method it's accessed. The SessionContext interface extends the javax.ejb.EJBContext interface. The methods provided by both interfaces are shown in Listing 11-11. The methods that SessionContext itself declares are in boldface; the rest are inherited from EJBContext. Several methods in the EJBContext interface have been deprecated and are no longer used; these methods are not shown.

Listing 11-11 The javax.ejb.SessionContext Interface (with Inherited Methods)
package javax.ejb;
import java.security.Principal;
public interface SessionContext extends EJBContext {

    public EJBLocalObject getEJBLocalObject();
    public EJBObject getEJBObject();

    public EJBLocalHome getEJBLocalHome();
    public EJBHome getEJBHome();

    public Principal getCallerPrincipal();
    public boolean isCallerInRole(String roleName);

    public UserTransaction getUserTransaction();
    public boolean getRollbackOnly();
    public void setRollbackOnly();

    public MessageContext getMessageContext();
    public TimerService getTimerService();

}

The EJB object methods, getEJBObject() and getEJBLocalObject(), and the EJB home methods, getEJBHome() and getEJBLocalHome(), return references to the EJB's remote and local interfaces. If you deploy the EJB as a Web services endpoint and do not define a remote or local interface, these methods will throw a java.lang.IllegalStateException when called.

The SessionContext.getCallerPrincipal() method returns a javax .security.Principal object, which represents the SOAP client's security identity. This method will return a Principal only if the SOAP client was authenticated before the call was dispatched to the bean instance. The Principal may be used for logging and other activities. The J2EE application server may use HTTP Basic Authentication or Symmetric HTTP to authenticate a SOAP client. These authentication mechanisms were discussed in detail in Section 10.2.3.2.1.

A caller's Principal may be associated with one or more roles. A role is a logical grouping of security identities, and a single Principal can be associated with many different roles. For example, a bank employee identified by Principal X might be associated with the three security roles of Teller, Bank Manager, and Human Resources Manager. Roles can be used to determine whether a Principal is authorized to perform a specific action. You can determine whether a Principal is associated with a specific role by invoking the isCallerInRole() method. The following snippet from Listing 11-5 shows how to ensure that only a Principal associated with the BankManager role may transfer more than $100,000.00 from one account to another.

package jwsed1.support;
import javax.ejb.SessionContext;
import javax.naming.InitialContext;
import java.util.Date;
import java.security.Principal;

public class MoneyTransferEJB implements javax.ejb.SessionBean {
  SessionContext ejbContext;
  public void create( ){}

  public void transfer(int acctA, int acctB, double amount){
    try{
      if(amount > 100000.0d && !ejbContext.isCallerInRole("BankManager")){
       Principal principal = ejbContext.getCallerPrincipal();
       throw new AuthorizationException(principal.getName()+
               " is not authorized to transfer over $100,000.00");
      }
      ...
  }
  ...
}

The SessionContext.getUserTransaction() method returns a javax .transaction.UserTransaction that can be used to begin, commit, and roll back transactions. This object is necessary if you want your EJB to manage its own transactions rather than using method-level declarative transactions. In practice, bean-managed transactions are rarely used, and the UserTransaction object is generally not needed. The EJB container provides a much simpler declarative transaction-management system tied to the methods themselves. Container-managed transactions allow you to declare, at development time, the transactional characteristics of methods.

The SessionContext.getRollbackOnly() method allows you to check the status of a container-managed transaction. This method will return true if the transaction has been marked for rollback. As you learned in Section 11.1, transactions can be propagated from one EJB to the next. Along the way, any one of them may invoke the setRollbackOnly() method, to mark the transaction for rollback and thus ensure that the transaction cannot commit. Another way to roll back a transaction is to throw a javax.ejb.EJBException or some other java.lang .RuntimeException. RuntimeExceptions always force a transaction to roll back and will also evict the offending instance from memory, ensuring that unstable bean instances are not used to handle subsequent client requests.

The SessionContext.getMessageContext() method allows an EJB endpoint to access a SAAJ representation of the SOAP message it's processing. In addition, it provides access to any properties set by its handler chain. This method is available only on the J2EE 1.4 platform. getMessageContext() returns a Message Context object, which is discussed in detail in Section 14.3.4.

The SessionContext.getTimerService() method returns an interface to the EJB Timer Service in the form of a TimerService object. This method is available only on the J2EE 1.4 platform. The EJB Timer Service allows you to schedule an EJB to receive a callback method at a specific time, or at repeating intervals. These features are convenient for scheduling-type operations.


     Python   SQL   Java   php   Perl 
     game development   web development   internet   *nix   graphics   hardware 
     telecommunications   C++ 
     Flash   Active Directory   Windows