Examples


This section should give the reader easy access to programming session beans. We shall present two simple examples. The first demonstrates the implementation of a stateless session bean. The second presents a stateful session bean.

Both examples use JDBC (Java database connectivity) for access to a database. JDBC is not the main focus of this book. In the following examples the program code for database access is presented only in brief. For a rapid introduction to JDBC we recommend the tutorial from Sun Microsystems [22].

The following examples should also run under EJB 1.1 without any changes. To run them under EJB 1.0, certain changes will be necessary:

  • In the bean class, instead of java.ejb.EJBException, java.rmi.RemoteException should be used to announce a system error.

  • EJB 1.0 does not understand the concept of resource factory reference. Therefore, the database connection must be opened directly using the correct database driver.

  • The deployment descriptor of EJB 1.0 consists of serialized objects and not of XML. Changes to account for this are necessary.

Stateless Session Beans

Overview

We are going to develop a simple session bean step by step. As our example we have chosen to perform a conversion between the euro and other currencies. The symbolic name of the bean is EuroExchangeSL (the "SL" in the name stands for "stateless," which seems appropriate for a transnational currency).

The bean offers a service on the server that performs the calculation with the aid of a conversion table. The table of exchange rates resides in a database. For buying and selling currencies two different exchange rates are used. The bean itself will not possess a conversational state. With each query it accesses the exchange table in the database.

First, we will define the home and remote interfaces. We will not present an additional implementation for the local interface, since the differences in programming are very slight. At the relevant points we will explain the differences. Next, we shall prepare the database and implement the bean class itself. Then we shall see to the deployment descriptor. Finally, we develop a simple client to test the bean.

Interfaces

To establish the bean's functionality we first define the interfaces that will be available to the client. Often, the program code for the home and remote interfaces with extensive commenting represents a sufficient specification of the bean.

The home and remote interfaces will be used on the client computer, and almost all the method calls will be relayed over the network to the server. Therefore, every method in these interfaces can trigger a RemoteException. The appearance of this exception signals the client that there is a network problem or a system error on the server.

The home interface inherits from the interface javax.ejb.EJBHome.In addition to the inherited methods, session beans define only create methods. In the case of stateless session beans there always exists only one create method without parameters. Every create method declares javax.ejb.CreateException in addition to java.rmi.RemoteException (Listing 4-16).

Listing 4-16: The home interface of the stateless session bean EuroExchangeSL.

start example
 package ejb.exchangeSL; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.EJBHome; public interface EuroExchangeHome extends EJBHome {     public EuroExchange create()         throws RemoteException, CreateException; } 
end example

If the session bean were to use a local interface, then EuroExchangeHome (see Listing 4-16) would inherit from EJBLocalHome instead of from EJBHome. Since communication with the session bean would take place in the same Java process, it would also be the case that the exception RemoteException would not be declared in any method.

The remote interface (see Listing 4-17) defines the methods of the application logic that the client can access. Our bean has two methods for converting currencies. The method changeFromEuro converts euros into other currencies, while the method changeToEuro converts in the other direction.

Listing 4-17: Remote interface of the stateless session bean EuroExchangeSL.

start example
 package ejb.exchangeSL; import java.rmi.RemoteException; import javax.ejb.EJBObject; public interface EuroExchange extends EJBObject {     public float changeFromEuro(String currency, float amount)         throws RemoteException;     public float changeToEuro(String currency, float amount)         throws RemoteException;     public void setExchangeRate(String currency, float euro, float foreignCurr)         throws RemoteException; } 
end example

There is also a method setExchangeRate, with whose help new exchange rates can be brought into the table. To keep our example simple, we will not implement a full-fledged management system for exchange rates. This method should merely provide a simple way to test the bean.

If the session bean were to use a local interface, then EuroExchange would inherit from EJBLocalObject instead of from EJBObject. Since communication with the session bean would take place in the same Java process, the exception RemoteException would not be declared in any method.

The Database

Our example accesses a database using JDBC. The database contains the table with exchange rates. The table is simply kept there and is generated, for example, with the SQL commands of Listing 4-18.

Listing 4-18: SQL commands for the exchange rate table.

start example
 CREATE TABLE EURO_EXCHANGE (CURRENCY CHAR(10) NOT NULL,  EURO REAL ,  FOREIGNCURR REAL ) ALTER TABLE EURO_EXCHANGE ADD CONSTRAINT  EURO_PRIM PRIMARY KEY (CURRENCY) 
end example

The Enterprise Bean

For the implementation of the bean class we use interfaces, classes, and exceptions from EJB. Therefore, we shall import parts of the ejb package. The use of the naming service requires the naming package. In accessing the database JDBC is used, for which the importation of parts of the sql package is required. In addition, we use the class DataSource from the package javax.sql as resource factory for database connections. The following list shows the necessary import instructions for implementing the bean class:

 import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.ejb.CreateException; import javax.ejb.EJBException; import javax.ejb.SessionBean; import javax.ejb.SessionContext; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.sql.DataSource; 

Since we are writing a session bean, our bean class implements the interface javax.ejb.SessionBean. Every method from the interface must be implemented in the bean class. All bean classes must be public and may not define a finalize method. The name of the bean class does not have to correspond to the name under which it appears in the naming service.

A state of the session bean is kept in instance variables, which are initialized upon the generation of an instance. This is a purely technical state, which does not relay the state of a client session, as is possible in the case of stateful session beans (see Listing 4-19). The context of the bean is placed in beanCtx, and in dataSource an object of the class javax.sql.DataSource, which relates to a resource factory for database connections to a particular database. The name of this resource factory is stored in the constant dbRef.

Listing 4-19: Class definition for EuroExchangeBean.

start example
 public class EuroExchangeBean implements SessionBean {     public static final String dbRef = "java:comp/env/jdbc/EuroDB";     private SessionContext beanCtx = null;     private DataSource dataSource = null;     ... } 
end example

First we look at the creation of a bean instance. The EJB container calls the method ejbCreate for each new bean instance. In the case of a stateless session bean the method ejbCreate has the task of initializing the bean instance. The bean instance reserves resources for itself that are necessary for its work. We mention once again that a stateless bean instance has no conversational state and can be used in turn by various clients. The initialization of a stateless session bean is therefore restricted to resources shared by all clients.

In our example (see Listing 4-20) the initial context of the naming service is created. Then the resource factory for the required database is read from the naming service, which is retained for the entire lifetime of the bean.

Listing 4-20: EuroExchangeBean.ejbCreate().

start example
 ...     public void ejbCreate()         throws CreateException     {         try {             Context c = new InitialContext();             this.dataSource = (DataSource)c.lookup(dbRef);         } catch(NamingException ex) {             String msg = "Cannot get Resource-Factory:"                        + ex.getMessage();             throw new CreateException(msg);         }     } ... 
end example

The counterpart to the method ejbCreate is the method ejbRemove (see Listing 4-21). The EJB container calls this method to let it know of its impending destruction. In this method it must be ensured that all resources that the bean instance uses are released. In our example the reference to the resource factory that was created in the method ejbCreate is released.

Listing 4-21: EuroExchangeBean.ejbRemove().

start example
 ...     public void ejbRemove() {         this.dataSource = null;     } ... 
end example

The simple example that we are presenting here does not use the session context that it receives from the EJB container. To support later extensions, however, the session context will be stored in the method setSessionContext (see Listing 4-22). The methods ejbActivate and ejbPassivate must be implemented to be in line with the interface Session-Bean. However, the implementation remains empty, since stateless session beans are neither activated nor passivated.

Listing 4-22: Methods for state management.

start example
  ...     public void ejbPassivate() {}     public void ejbActivate() {}     public void setSessionContext(SessionContext ctx) {         this.beanCtx = ctx;     } ... 
end example

The implementation of the application methods is now comparatively easy. Through the interplay of ejbCreate and ejbRemove the application methods are freed from responsibility for the naming service and DataSource.

The method changeFromEuro has the task of converting an amount in euros to another currency. To begin, a database connection must be established using the resource factory DataSource. Then a Statement for the execution of database accesses is created. The result is returned in ResultSet. At the end of the method, Connection, Statement, and ResultSet are closed in the finally block.

The amount and chosen currency are passed as parameters. The method reads the required exchange rate from the database and then calculates the value, which it returns. If an error occurs, an EJBException is triggered. The EJB container relays this as a RemoteException. If the bean were to use a local interface, then the EJB container would itself relay the EJBException. Listing 4-23 shows the implementation. The method changeToEuro works analogously and is therefore not displayed.

Listing 4-23: EuroExchangeBean.changeFromEuro(...).

start example
 ...     public float changeFromEuro(String currency, float amount) {         if(currency == null) {             throw new EJBException("illegal argument: currency");         }         final String query =           "SELECT FOREIGNCURR FROM EURO_EXCHANGE WHERE CURRENCY=?";         Connection con = null;         PreparedStatement st = null;         ResultSet rs = null;         try {             con = this.dataSource.getConnection();             st = con.prepareStatement(query);             st.setString(1, currency);             rs = st.executeQuery();             if(!rs.next()) {                 throw new EJBException("no such currency:" + currency);             }             return amount * rs.getFloat(1);         } catch(SQLException ex) {             ex.printStackTrace();             throw new EJBException("db-error:" + ex.getMessage());         } finally {             try { rs.close(); } catch(Exception ex) {}             try { st.close(); } catch(Exception ex) {}             try { con.close(); } catch(Exception ex) {}         }     } ... 
end example

With setExchangeRate only a simple test of our example is possible. The method can be used to write exchange rates into the database. In case of error, again an EJBException is triggered. Listing 4-24 shows the implementation.

Listing 4-24: EuroExchangeBean.setExchangeRate(...).

start example
 ...     public void setExchangeRate(String currency,                                 float euro,                                 float foreignCurr)     {         if(currency == null) {             throw new EJBException("illegal argument: currency");         }         final String delQuery =             "DELETE FROM EURO_EXCHANGE WHERE CURRENCY=?";         final String insQuery =             "INSERT INTO EURO_EXCHANGE" +             "(CURRENCY, EURO, FOREIGNCURR) VALUES(?, ?, ?)";         Connection con = null;         PreparedStatement del = null;         PreparedStatement ins = null;         boolean success = false;         try {             con = this.dataSource.getConnection();             con.setAutoCommit(false);             del = con.prepareStatement(delQuery);             del.setString(1, currency);             del.executeUpdate();             ins = con.prepareStatement(insQuery);             ins.setString(1, currency);             ins.setFloat(2, euro);             ins.setFloat(3, foreignCurr);             ins.executeUpdate();             success = true;         } catch(SQLException ex) {             ex.printStackTrace();             throw new EJBException("db-error:" + ex.getMessage());         } finally {             if(success) {                 try { con.commit(); } catch(Exception ex) {}             } else {                 try { con.rollback(); } catch(Exception ex) {}             }             try { del.close(); } catch(Exception ex) {}             try { ins.close(); } catch(Exception ex) {}             try { con.setAutoCommit(true); } catch(Exception ex) {}             try { con.close(); } catch(Exception ex) {}         }     } 
end example

This completes the implementation of the bean class. All methods for identity management, state management, and application logic have been implemented.

Deployment

In order for the bean to be used by a client, it must be installed in the EJB container (Deployment). To this end information about the bean is collected in the deployment descriptor in XML syntax (see Listing 4-25). The bean developer, the application assembler, and the deployer are responsible for this. Most developers of application servers offer tools that simplify the creation of such deployment descriptors through the use of graphical interfaces. Nonetheless, it is common practice to create deployment descriptors "by hand" (with a text editor).

Listing 4-25: Deployment descriptor for the session bean EuroExchangeSL.

start example
 <?xml version="1.0" ?> <ejb-jar version="2.1" 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">   <description>     This deployment descriptor contains information     about session bean exchange.   </description>   <enterprise-beans>     <session>       <ejb-name>Exchange</ejb-name>       <home>ejb.exchangeSL.EuroExchangeHome</home>       <remote>ejb.exchangeSL.EuroExchange</remote>       <ejb-class>ejb.exchangeSL.EuroExchangeBean</ejb-class>       <session-type>Stateless</session-type>       <transaction-type>Bean</transaction-type>       <resource-ref>         <description> Euro-database </description>         <res-ref-name>jdbc/EuroDB</res-ref-name>         <res-type>javax.sql.DataSource</res-type>         <res-auth>Container</res-auth>       </resource-ref>     </session>   </enterprise-beans>   <assembly-descriptor>   </assembly-descriptor> </ejb-jar> 
end example

In the deployment descriptor the bean developer needs to provide only information that the EJB container cannot itself provide through the examination of the classes (Introspection). First he establishes that the subject is a session bean (session), and gives the bean a unique name (ejb-name). When the deployer installs the bean later in the EJB container, this name is frequently used to register the bean with the naming service.

Then he declares the names of the required classes and interfaces. He defines the home interface (home) and the remote interface (remote) as well as the implementation class of the bean (ejb-class). Were the bean to use local interfaces, then instead of home, the element local-home would be used, and instead of remote, local, to declare the classes for the local home and local interfaces.

The EJB container requires information as to whether a stateful or stateless session bean is involved (session-type: Stateful/Stateless). In our simple example the bean takes over the transaction control (transaction-type) itself.

Moreover, the bean developer must define the reference to the requisite database (resource-ref). He defines a logical name (res-ref-name) relative to the environment of the bean (java:comp/env) and sets the data type of the resource factory (res-type) and determines that the EJB container should set the security strategy (res-auth). In addition, he describes in a comment the task of this reference (description) in order to make the job of the deployer possible.

The task of the bean provider is now complete. He has prepared all necessary information about the bean. The application assembler could define the access rights and determine the interplay with other beans. For our simple example these definitions are not absolutely necessary.

As a rule, further information is necessary for deployment, which varies from application server to application server. Thus, for example, it must be determined under what name the bean is to appear in JNDI. The resource reference must be mapped to an existing database, which contains the required tables. In many cases it is necessary to inform the EJB container about parameters for the run-time environment, for example, the size of the instance pool, cache size, or timeout values. The nature and extent of such additional specifications vary, as we have said, from product to product, as also the way in which these specifications must be made. Listing 4-26 presents an imaginary example of how additional specifications for deployment of the EuroExchange bean can be made.

Listing 4-26: Definitions for the deployment of an EJB container.

start example
 <bean>   <ejb-name>Exchange</ejb-name>   <stateless-session-descriptor>     <max-pool-size>100</max-pool-size>   </stateless-session-descriptor>   <resource-reference-description>     <res-ref-name>jdbc/EuroDB</res-ref-name>     <jndi-name>jdbc/Oracle_Test_DB</jndi-name>   </resource-reference-description>   <jndi-name>     ExchangeSL   </jndi-name> <bean> 
end example

The Client

For the session bean EuroExchangeSL a simple client will be developed (see Listing 4-27). It defines the exchange rate for the U.S. dollar, according to which it will convert dollars into euros and vice versa. Our client requires access to the naming service javax.naming and must be able to cope with network errors (javax.rmi.RemoteException). Furthermore, the client requires the bean interfaces with which it wants to work. We have collected these interfaces in the package ejb.exchangeSL. The bean's JNDI name is ExchangeSL. This name was associated with the bean via the additional deployment instructions in Listing 4-26.

Listing 4-27: Client program for the session bean EuroExchangeSL.

start example
 package ejb.exchangeSL.client; import ejb.exchangeSL.EuroExchange; import ejb.exchangeSL.EuroExchangeHome; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.RemoveException; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.rmi.PortableRemoteObject; public class Client {     private EuroExchange exchange;     public Client() { }     public void init()         throws NamingException, RemoteException, CreateException     {         java.util.Properties p = new java.util.Properties();         p.put(Context.INITIAL_CONTEXT_FACTORY,                "JNDI-PROVIDER-CLASS");         p.put(Context.PROVIDER_URL, "JNDI-URL");         Context ctx = new InitialContext(p);         Object o = ctx.lookup("ExchangeSL");         EuroExchangeHome home =             (EuroExchangeHome)PortableRemoteObject.narrow(o,                               EuroExchangeHome.class);         this.exchange = home.create();     }     public void run() {         try {             this.exchange.setExchangeRate("US-Dollar", 2f, 0.5f);         } catch(RemoteException ex) {             ex.printStackTrace();             return;         }         System.out.println("Changing 100 US-Dollars to Euro");         float amount = 0f;         try {             amount = this.exchange.changeToEuro("US-Dollar", 100);         } catch(RemoteException ex) {             ex.printStackTrace();             return;         }         System.out.println("Result: " + amount);         System.out.println("Changing " + amount +                            " Euro to US-Dollars");         float n_amount = 0f;         try {             n_amount =                 this.exchange.changeFromEuro("US-Dollar", amount);         } catch(RemoteException ex) {             ex.printStackTrace();             return;         }         System.out.println("Result: " + n_amount);     }     public void cleanUp() {         try {             this.exchange.remove();         } catch(RemoteException ex) {             ex.printStackTrace();         } catch(RemoveException ex) {             ex.printStackTrace();         }     }     public static void main(String[] args) {         Client c = new Client();         try {             c.init();         } catch(Exception ex) {             ex.printStackTrace();             return;         }         c.run();         c.cleanUp();     } } 
end example

Were the bean to use local interfaces instead of remote ones, not much would change in the client programming. The client would then have to use PortableRemoteObject.narrow only if the naming service were not going to run in the process of the application server. As a rule, the naming service of the application server is used. If the client uses local interfaces, it must be located in the same process as the enterprise bean, that is, in the process of the application server. In this case it could use type casting to convert the return value of the method lookup directly into the desired type. Moreover, in the case of local interfaces the declaration of java.rmi.RemoteException does not apply. As we have already mentioned, the main difference in the use of local interfaces has less to do with programming than with run-time behavior. On the one hand, RMI overhead is avoided, since the client is located in the same process, while on the other hand, the calling parameters and return values are passed not as call by value but as call by reference (with the exception of Java's primitive data types, which are always passed as call by value).




Enterprise JavaBeans 2.1
Enterprise JavaBeans 2.1
ISBN: 1590590880
EAN: 2147483647
Year: 2006
Pages: 103

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net