Home

MassLight presents

Chapter 6. Enterprise JavaBeans

Abstracting database objects

Goals After completing this chapter, the student will be able to
  • explain the basic conceptual models of Enterprise JavaBeans.
  • use an Enterprise JavaBean.
  • implement session Enterprise JavaBeans.
  • implement entity Enterprise JavaBeans.
Prerequisites In order to complete this chapter successfully, the student must have a basic knowledge of databases.
Objectives The purpose of this chapter is to provide an overview of EJBs, and information about session and entity EJBs.

What Are Enterprise JavaBeans?

Enterprise JavaBeans (EJBs) are a part of the Java 2 Enterprise Edition specifications. EJBs provide developers with a distributed, object-oriented, component-based architecture. Developers commonly use an EJB to represent a business object or a table in a database. Part of an application can be built by creating some EJBs to model the different tables in the database and other EJBs to handle the non-persistent business logic. In terms of the model-view-controller model, EJBs can be used to represent the model portion of an application.

The EJB Architecture

The basic EJB architecture is composed of an EJB server, EJB containers, EJB clients, Enterprise JavaBeans, and a helper directory and naming service (such as JNDI). Enterprise JavaBeans reside within an EJB container and EJB containers reside in the EJB server. The client does not directly invoke methods of the EJB, instead the container acts as an intermediary between the EJB and the client.


EJB servers Composed of server software, the EJB server is analogous to the Object Request Broker (ORB) from the CORBA world. The server provides a way for the client to transact with the EJBs. It also provides naming services.
EJB containers Located inside the EJB server, the EJB containers provide a means for invoking an EJB’s methods. An EJB client can send a request to invoke a method on an EJB to the EJB container. The EJB container invokes the appropriate method on the EJB. Containers provide support for security, object persistence, resource pooling, and transaction processing.
EJB clients EJB clients find EJBs through a naming and directory service. The client then sends a message to an EJB container, which determines the EJB method that the client wants to invoke.
Enterprise JavaBeans Enterprise JavaBeans can be session beans, entity beans, or message-driven beans. We will discuss these more in the next section. EJBs reside within EJB containers.
Directory service The directory/naming service provides the ability to locate any object registered with the directory/naming service. It provides a mapping between a given name of an entity and, in the case of EJBs, a stub that corresponds to the home interface of an Enterprise JavaBean (this will be discussed later in the chapter).

Types of Enterprise JavaBeans

Session Beans
Session beans are used to represent non-persistent business logic. They almost always have an association with a particular client. If the container is shutdown, the session beans running are lost. Session beans can access and modify persistent data, though entity beans are usually more suitable for working with persistent data.

Entity Beans
In general, developers use entity beans to represent persistent business logic. Entity beans can be shared amongst multiple clients and persist if the client is destroyed or if the server or container is shutdown. Entity beans commonly represent tables of a database. The properties of a bean class represent the different columns of the table. When an instance of an entity EJB is created, the instance can be thought of as temporarily representing a row in the table.

Message-driven Beans
Message-driven beans are an added feature of the EJB 2.0 specification. They are not discussed in this chapter. A message-driven bean's primary ability is to execute some section of code upon receiving a receipt from a single client message. They are stateless and therefore non-persistent. Also, message-driven EJBs do not survive the crash of an EJB container.

Passivation/Activation
An EJB container can manage its primary memory allocation by passivating and activating the EJBs that it manages. Passivation saves the state of a bean to persistent storage, and swaps it out of memory. Activation is just the opposite of passivation: the state of a bean is restored from persistent storage and is swapped into memory. The processes of passivation and activation apply to stateful session beans and all entity beans. Before the EJB is passivated and after the EJB is activated, the container calls the ejbPassivate() or ejbActivate(), respectively, of the EJB's bean class so that the EJB can perform any necessary setup to handle the EJB's change of state.

Persistence
Stateful session beans and entity beans are persistent. The EJB container or the bean class of the EJB provides the necessary functionality to support the persistence of the bean's state. To save a bean, we can get a javax.ejb.Handle object for the bean by invoking the EJB's getHandle() method. Restoration is accomplished by invoking the getEJBObject() method on the Handle object.

The four basic components of an EJB

Introduction
The four main components of an EJB are the home interface, the remote interface, the bean class, and the deployment descriptor.

The home interface
The home interface declares the methods for creating, finding, and destroying instances of EJBs. The methods declared in the home interface must correspond to certain methods implemented in the bean class.

The remote interface
The remote interface declares the methods that contain the EJB's business logic. The methods declared in the remote interface must be implemented in the bean class.

The bean class
The bean class provides the logic of the bean. It must implement the methods declared in the remote interface and must implement methods corresponding to the methods in the home interface.

The deployment descriptor
The deployment descriptor is always called ejb-jar.xml.

The deployment descriptor describes EJB(s) to the EJB container. In this chapter we will be deploying only one EJB at a time, so there will be only one EJB described in each deployment descriptor. With JBoss, the .jar file of a single EJB can be deployed directly to the EJB container. This is the method of deployment that will be taken in this chapter, though normally an EJB would need to be deployed as part of an enterprise application archive (.ear). We will see how to create an .ear file in the next chapter.

Naming the components of an EJB

If the EJB is named Demo, then the remote interface should be Demo.java, the home interface should be DemoHome.java, the bean class should be either DemoBean.java or DemoEJB.java. The deployment descriptor should always be ejb-jar.xml.


element
description
location in EJB .jar file
the home interface used for creating new enterprise beans, locating existing enterprise beans, and destroying enterprise beans A Java interface located in a package in the EJB’s jar file
the remote interface used for declaring the business methods of the enterprise bean A Java interface located in a package in the EJB’s jar file
the bean class used for encapsulating the application logic of the bean and implementing the business methods defined in the remote interface A Java class located in a package in the EJB’s jar file
XML deployment descriptor resides in an XML configuration file that describes the enterprise bean(s) to the EJB server – using a deployment tool eliminates many of the steps needed to create the deployment descriptor by hand Named ejb-jar.xml, and located in the META-INF directory of the EJB’s jar file

Deploying an EJB

In order for a session or an entity EJB to be deployed, four parts are needed: the remote interface, the home interface, the bean class, and the deployment descriptor. The two interfaces and the bean class are placed in a package and the deployment descriptor is placed in the META-INF directory. The package and the META-INF/ejb-jar.xml are jarred into an EJB .jar file. When the EJB is deployed, the four parts reside in the EJB container.
 


The EJB Specification

The most current versions of the EJB specification can be found at Sun’s web site. A PDF or Postscript version of the specification can be downloaded at http://java.sun.com/products/ejb/docs.html.

How EJBs Work

How the client interacts with an EJB

When the EJB is deployed by the container, the container registers the given name of the home interface to the naming directory. When a client requests the given name of the home interface, the naming directory returns a stub for the home interface. The home stub does not actually interact with the home interface at all. Instead, the home stub interacts with the container's implementation of the home interface. The client uses the home stub to call the create, remove, and/or find methods declared by the home interface. When a create or find method is called successfully, the container will create an instance of the bean class and the home implementation will return a remote stub, which corresponds to an implementation of the remote interface. The client can then use the remote stub to make calls to the EJB.

What are stubs?

A stub is a part of Remote Method Invocation (RMI) technology. A client can use a stub to remotely invoke exposed methods of an object that exists in a location separate from the client. The stub will receive the return value from the method invoked from the remote object and pass the value to the client, if a value is returned.

Interacting with an EJB

An EJB client must perform all the client's actions outlined in the previous diagram. Each necessary line of code is explained.

Set up the naming provider - values are for JBoss/Tomcat

System.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory");
System.setProperty("java.naming.provider.url", "localhost:1099");

Get a naming context - in a try/catch statement

InitialContext jndiContext = new InitialContext();

Get a reference to the home stub - in a try/catch statement

Object reference = jndiContext.lookup("EJBName");

Get the home stub - in a try/catch statement

com.masslight.ExampleEJB.Home home = (com.masslight.ExampleEJB.Home
   PortableRemoteObject.narrow (reference, com.masslight.ExampleEJB.Home.class);

Get the remote stub - in a try/catch statement

com.masslight.ExampleEJB.Remote remote = home.create();

Invoke a business method from an instance of the bean class - in a try/catch statement

remote.businessMethodOne(...);

EJB Deployment Descriptors

A deployment tool should be used to create the deployment descriptor but it is important to understand how to create a basic deployment descriptor by hand. For a more thorough description go to http://java.sun.com/dtd/ejb-jar_2_0.dtd.

Version and document tags

This pair of tags is required at the top of all deployment descriptors.

<?xml version="1.0" encoding="Cp1252"?>
<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

The ejb-jar tags

These tags should follow the version and document tags. A deployment descriptor can describe more than one EJB. In this chapter's examples, only one EJB is described per deployment descriptor. The session tag is used to describe a session EJB and the entity tag is used to describe an entity EJB. display-name specifies a name that is intended to be displayed by tools.

<ejb-jar>
  <display-name>ExampleEJBs</display-name>
  <enterprise-beans>
    ...
    <session>
      ...
    </session>
    ...
    <entity>
      ...
    </entity>
    ... 
  </enterprise-beans>
</ejb-jar>

The name of the bean

These tags should reside within both session and entity tags. display-name specifies a name that is intended to be displayed by tools. ejb-name is the name that will be registered with the directory service - this is the name that will be used by the EJB client to locate the EJB.

<display-name>Hello</display-name>
<ejb-name>Hello</ejb-name>

The classes of the bean

These tags should reside within both session and entity tags. The classes used for the home interface, the remote interface, and the bean class need to be declared. All three should be fully qualified class names.

<home>com.masslight.HelloEJBClasses.HelloHome</home>
<remote>com.masslight.HelloEJBClasses.Hello</remote>
<ejb-class>com.masslight.HelloEJBClasses.HelloBean</ejb-class>

Session bean-specific tags

These tags should reside within a session tag. session-type should be either Stateless or Stateful. transaction-type should be Bean.

<session-type>Stateless</session-type>
<transaction-type>Bean</transaction-type>

Entity bean-specific tags

These tags should reside within an entity tag. persistence-type should be Bean. prim-key-class should be the fully qualified Java class of the primary key of the bean class. reentrant should be False.

<persistence-type>Bean</persistence-type>
<prim-key-class>java.lang.Integer</prim-key-class>
<reentrant>False</reentrant>

Session Beans

Stateful versus Stateless

Session beans can be either stateless or stateful. A stateful session bean can have properties with accessor and mutator methods and most importantly, must declare that it is a Stateful Session Bean in the deployment descriptor. Stateless beans, on the other hand, must declare themselves in the deployment descriptor file as a Stateless Session Bean. If a session bean is declared as stateless, then EJB container will treat it as such.

Stateless session beans have no internal state. For this reason, there is no need for them to be passivated. Stateful session beans do have an internal state and therefore need to be able to handle passivation and activation. A stateless session bean can be pooled to service multiple clients, while a stateful session bean can associate with only one client at a time.

Stateless Session Bean Stateful Session Bean
has no properties with accessor or mutator methods in the bean class has properties with accessor and mutator methods in the bean class
declared Stateless Session Bean in the deployment descriptor declared Stateful Session Bean in the deployment descriptor
multiple clients per instance of a bean single client per instance of a bean

Implementing a Session Bean

Session beans require a home interface, a remote interface, a bean class, and an XML deployment descriptor. All four parts need to follow certain guidelines in order to be correctly deployed by the EJB container. The home interface extends the javax.ejb.EJBHome interface and the remote interface extends the javax.ejb.EJBObject interface. The bean class needs to implement the javax.ejb.SessionBean interface, which the EJB specification defines as the following, along with its superclass, javax.ejb.EnterpriseBean:

interface javax.ejb.EnterpriseBean

public interface EnterpriseBean extends java.io.Serializable {}

interface javax.ejb.SessionBean

public interface SessionBean extends EnterpriseBean { 
public abstract void ejbActivate() throws java.rmi.RemoteException; 
public abstract void ejbPasivate() throws java.rmi.RemoteException;
public abstract void ejbRemove() throws java.rmi.RemoteException;
public abstract void setSessionContext(SessionContext ctx) throws java.rmi.RemoteException;
}

Below are tables detailing the necessary elements of the remote interface, the home interface, and the bean class of stateless and stateful session beans.

home interface extends javax.ejb.EJBHome

method name
returns
Description
create() throws java.rmi.RemoteException, javax.ejb.CreateException the stub for the remote interface When the container receives this call, it creates an instance of the session bean - the create method can include parameters, as long as each create method declaration matches the implementation of an ejbCreate method in the bean class. There must be at least one create method, but there can be more than one. Required.

remote interface extends javax.ejb.EJBObject

method name
returns
description
exampleMethod(int GNPofFranceInBillionsOfPounds); int All methods declared in this interface are specific to a particular EJB. Each method declared here must also be implemented in the bean class. Note: this is an example method only used to show that methods declared in the remote interface must be implemented in the bean class. Not Required.

bean class implements javax.ejb.SessionBean

method name
returns
Description
ejbCreate() throws javax.ejb.CreateException void Must correspond to the create method declared in the home interface. The container calls the corresponding ejbCreate method after a client has called the create method. Required. 
ejbRemove() void The container calls this method to alert the instance of the bean that it is going to be destroyed. The instance of the bean can then clean up before being removed. Required.
ejbActivate() void The container calls this method to alert the instance of the bean that it is being activated so that the instance of the bean can respond accordingly, such as restoring network connections and handles. Required.
ejbPassivate() void The container calls this method to alert the instance of the bean that it has been passivated so that the instance of the bean can respond accordingly, such as releasing network connections and handles. Required.
setSessionContext() void The container calls this method after the bean has been created but before any of the ejbCreate methods have been called. The bean is responsible for storing the javax.ejb.SessionContext object. Required.
exampleMethod(int GNPofFranceInBillionsOfPounds) int This example method would need to be implemented in the bean class if it is declared in the remote interface. Not Required

Hello: a simple EJB application

(If you have not already done so, you can download this and other examples used in this course. Mac OS X or other UNIX users click here instead.)

Do this:

  1. Create the directory structure. The root directory should be Hello. Within that, we will have two separate directories, HelloClient and HelloEJB. HelloClient looks like the web applications we've been buildin throughout the course: it has JSP files, a WEB-INF directory with the Struts framework, and a classes directory with nested directories to represent the two packages we'll be using: com.masslight.actions and com.masslight.beans.
  2. Copy the Struts framework into Hello/HelloClient/WEB-INF.
code/Chapter6/Hello/HelloClient
HelloClient
  |
  +-- build.xml (*)
  |
  +-- index.jsp (*)
  |
  +-- WEB-INF
        |
        +-- web.xml (*)
        |
        +-- app.tld (*)
        |
        +-- struts-bean.tld (*)
        |
        +-- struts-config.xml (*)
        |
        +-- struts-form.tld (*)
        |
        +-- struts-html.tld (*)
        |
        +-- struts-logic.tld (*)
        |
        +-- struts-template.tld (*)
        |
        +-- struts.tld (*)
        |
        +-- classes
        |     |
        |     +-- com
        |           |
        |           +-- masslight
        |                 |
        |                 +-- beans
        |                 |     |
        |                 |     +-- HelloClientBean.java (*)
        |                 |
        |                 +-- actions
        |                       |
        |                       +-- GetBeanAction.java (*)
        |
        +-- lib
              |
              +-- struts.jar (*)

(*) denotes a file

  1. Create the JSP in the HelloClient directory (not the root Hello directory!)
code/Chapter6/Hello/HelloClient/index.jsp
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<html:html>
   <head>
     <title>
       EJB example
     </title>
     <html:base/>
   </head>
   <body>
     <logic:present name="greeting">
         <bean:write name="greeting"/>
     </logic:present>
   </body>
</html:html>

  1. Create the web.xml file in HelloClient/WEB-INF/. Nothing unusual here; we've seen all this before.
code/Chapter6/Hello/HelloClient/WEB-INF/web.xml
<?xml version="1.0" encoding="ISO-8859-1"?> 
<!DOCTYPE web-app 
  PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" 
  "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> 

<web-app> 

  <!-- Action Servlet Configuration --> 
  <servlet> 
    <servlet-name>action</servlet-name> 
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> 
    <init-param> 
      <param-name>application</param-name> 
      <param-value>com.masslight.ApplicationResources</param-value> 
    </init-param> 
    <init-param> 
      <param-name>config</param-name> 
      <param-value>/WEB-INF/struts-config.xml</param-value> 
    </init-param> 
    <init-param> 
      <param-name>debug</param-name> 
      <param-value>2</param-value> 
    </init-param>
    <init-param> 
      <param-name>detail</param-name> 
      <param-value>2</param-value> 
    </init-param> 
    <init-param> 
      <param-name>validate</param-name> 
      <param-value>true</param-value> 
    </init-param> 
    <load-on-startup>2</load-on-startup> 
  </servlet> 

  <!-- Action Servlet Mapping --> 
  <servlet-mapping> 
    <servlet-name>action</servlet-name> 
    <url-pattern>*.do</url-pattern> 
  </servlet-mapping> 

  <!-- Struts Tag Library Descriptors --> 
  <taglib> 
    <taglib-uri>/WEB-INF/struts-bean.tld</taglib-uri> 
    <taglib-location>/WEB-INF/struts-bean.tld</taglib-location> 
  </taglib> 

  <taglib> 
    <taglib-uri>/WEB-INF/struts-html.tld</taglib-uri> 
    <taglib-location>/WEB-INF/struts-html.tld</taglib-location> 
  </taglib> 

  <taglib> 
    <taglib-uri>/WEB-INF/struts-logic.tld</taglib-uri> 
    <taglib-location>/WEB-INF/struts-logic.tld</taglib-location> 
  </taglib> 

</web-app>

  1. Create the struts-config.xml file. Again, this should look very familiar. It's not identical to previous examples, but it's pretty close. The action path is different, and points to a different classname of JavaBean, but that's about it.
code/Chapter6/Hello/HelloClient/WEB-INF/struts-config.xml
<?xml version="1.0" encoding="ISO-8859-1" ?> 
<!DOCTYPE struts-config PUBLIC 
          "-//Apache Software Foundation//DTD Struts Configuration 1.0//EN" 
          "http://jakarta.apache.org/struts/dtds/struts-config_1_0.dtd"> 

<struts-config> 
 

  <!-- ========== Global Forward Definitions ============================== --> 
  <global-forwards> 
    <forward name="success" path="/index.jsp"/> 
  </global-forwards> 

  <!-- ========== Action Mapping Definitions ============================== --> 
  <action-mappings> 
    <!-- Save user registration --> 
    <action path="/getEJB" 
            type="com.masslight.actions.GetBeanAction"  />

  </action-mappings> 

</struts-config>

  1. Create the Struts Action class for this JSP. This is where things start to get interesting. In previous examples, this was where we did all the work. But here, the Action class is calling the HelloClientBean and telling it to do the work.
code/Chapter6/Hello/HelloClient/WEB-INF/classes/com/masslight/actions/GetBeanAction.java
package com.masslight.actions;

import java.util.Vector;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import java.util.Hashtable;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.util.*;

import com.masslight.beans.HelloClientBean;

public final class GetBeanAction extends Action {

    // The constructor method for this class
    public GetBeanAction() {
    }

    // This sets the list as a session bean
    public ActionForward perform(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response)
    throws IOException, ServletException {

        HttpSession session = request.getSession();

        HelloClientBean clientbean = new HelloClientBean();
        clientbean.startup();
        session.setAttribute("greeting", clientbean.sayGreeting("Hello"));

        return (mapping.findForward("success"));
    }
}

  1. Create the client bean. Note that this doesn't do any real work either (and by "real work", I mean "business logic"); instead, it gets a reference to the EJB and tells it to do the work.
code/Chapter6/Hello/HelloClient/WEB-INF/classes/com/masslight/beans/HelloClientBean.java
package com.masslight.beans;

import javax.naming.*;
import java.util.Hashtable;
import javax.rmi.PortableRemoteObject;
import javax.ejb.*;

public class HelloClientBean
{
    com.masslight.HelloEJBClasses.Hello hello = null;
    com.masslight.HelloEJBClasses.HelloHome home = null;

    public HelloClientBean() {

    }

    /**
        This method does all the work. It creates an instance of the hello EJB on
     the EJB server, and calls its `sayHello()' method, then prints
     the result the returned String.
     */
    public void startup () {
        // Set up the naming provider; this may not always be necessary, depending
        // on how your Java system is configured.
        System.setProperty("java.naming.factory.initial",
                           "org.jnp.interfaces.NamingContextFactory");
        System.setProperty("java.naming.provider.url",
                           "localhost:1099");

        // A single `try' block is not an ideal way  to do exception handling, but it
        // is easier to read code without all the catch blocks
        try
        {
            // Get a naming context
            InitialContext jndiContext = new InitialContext();
            System.out.println("Got context \n");

            // Get a reference to the hello Bean
            Object ref  = jndiContext.lookup("Hello");
            System.out.println("Got reference \n");

            // Get a reference from this to the Bean's Home interface
            home = (com.masslight.HelloEJBClasses.HelloHome)
                PortableRemoteObject.narrow (ref, 
		  com.masslight.HelloEJBClasses.HelloHome.class);

            // Create an hello object from the Home interface
            hello = home.create();

        }   catch(Exception e)
        {
            System.out.println(e.toString());
        }
    }
    public String sayGreeting(String greeting) {
        String output = new String("");
        try {
            output += hello.sayHello(greeting);
        }  catch(Exception e)    {
            System.out.println(e.toString());
        }
        return(output);
    }
}

  1. Create a directory structure to hold the EJB code. The root directory, HelloEJB, should be in the same directory as the HelloClient directory we created for the client code. Within that, we need a META-INF directory for the EJB descriptor, and a directory hierarchy to represent the path to the EJB classes themselves, which is com.masslight.HelloEJBClasses.
code/Chapter6/Hello/HelloEJB
HelloEJB
  |
  +-- build.xml (*)
  |
  +-- META-INF
  |     |
  |     +-- ejb-jar.xml (*)
  |
  +-- com
        |
        +-- masslight
              |
              +-- HelloEJBClasses
                    |
                    +-- HelloBean.java (*)
                    |
                    +-- Hello.java (*)
                    |
                    +-- HelloHome.java (*)

(*) denotes a file

  1. Create the home interface.
code/Chapter6/Hello/HelloEJB/com/masslight/HelloEJBClasses/HelloHome.java
package com.masslight.HelloEJBClasses;
import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

/**
This interface defines the `Home' interface for the `Hello' EJB.
*/

public interface HelloHome extends EJBHome
{
    /**
    Creates an instance of the `HelloBean' class on the server, and returns a
     remote reference to an hello interface on the client.
     */
    Hello create() throws RemoteException, CreateException;
}

  1. Create the remote interface.
code/Chapter6/Hello/HelloEJB/com/masslight/HelloEJBClasses/Hello.java
package com.masslight.HelloEJBClasses;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;

/**
 This interface defines the `Remote' interface for the `hello' EJB. Only methods in 
 this interface are exposed to the outside world. The class
 HelloBean implements the methods of this interface.
*/

public interface Hello extends EJBObject
{
    public String sayHello(String greeting) throws RemoteException;
}

  1. Create the EJB class, which contains the business logic (i.e. "real work"). In this case, our only business logic is the sayHello method; everything else is standard required methods that every EJB must have. Note that we don't do anything in the methods, but they have to be there anyway.
code/Chapter6/Hello/HelloEJB/com/masslight/HelloEJBClasses/HelloBean.java
package com.masslight.HelloEJBClasses;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class HelloBean implements SessionBean
{
    public String sayHello(String greeting)
    {
        return(greeting);
    }
    public HelloBean() {}
    public void ejbCreate() {}
    public void ejbRemove() {}
    public void ejbActivate() {}
    public void ejbPassivate() {}
    public void setSessionContext(SessionContext sc) {}
}

  1. Create the deployment descriptor, which specifies that this is a stateless session bean with the home interface, remote interface, and EJB class that we just defined in the previous 3 files.
code/Chapter6/Hello/HelloEJB/META-INF/ejb-jar.xml
<?xml version="1.0" encoding="Cp1252"?>

<!DOCTYPE ejb-jar PUBLIC 
  '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 
  'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar>
  <display-name>Hello</display-name>
  <enterprise-beans>
    <session>
      <display-name>Hello</display-name>
      <ejb-name>Hello</ejb-name>
      <home>com.masslight.HelloEJBClasses.HelloHome</home>
      <remote>com.masslight.HelloEJBClasses.Hello</remote>
      <ejb-class>com.masslight.HelloEJBClasses.HelloBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Bean</transaction-type>
      <security-identity>
        <description></description>
        <use-caller-identity></use-caller-identity>
      </security-identity>
    </session>
  </enterprise-beans>
</ejb-jar>

  1. Create the build scripts for the client code, the EJB, and the master script that ties them all together.
code/Chapter6/Hello/HelloClient/build.xml
<project name="HelloClient" default="dist" basedir=".">

 <!-- set global properties for this build -->
 <property environment="env"/>
 <property name="top" value="."/>
 <property name="src" value="."/>
 <property name="build" value="build"/>
 <property name="dist" value="dist"/>
 <property name="war_dir" value="${dist}/lib"/>
 <property name="war_file" value="${war_dir}/HelloClient.war"/>

 <property name="webinf" value="${top}/WEB-INF"/>
 <property name="web.xml" value="${webinf}/web.xml"/>
 <property name="classes" value="${webinf}/classes"/>
 <property name="lib" value="${top}/WEB-INF/lib"/>
 <property name="struts.jar" value="${env.STRUTS_HOME}/lib/struts.jar"/>
 <property name="servlet.jar" value="${env.TOMCAT_HOME}/lib/servlet.jar"/>
 <property name="ejb.src" value="${top}/../HelloEJB"/>
 <property name="ejb.jar" value="${env.JBOSS_HOME}/lib/ext/jboss-j2ee.jar"/>
 <property name="deploy" value="${env.JBOSS_HOME}/deploy"/>

 <target name="clean">
   <!-- Delete our the ${build} and ${dist} directory trees -->
   <delete dir="${build}"/>
   <delete dir="${dist}"/>
   <delete dir="${war_dir}"/>
 </target>

 <target name="init">
   <!-- Create the build directory structure used by compile and dist -->
   <mkdir dir="${build}"/>
   <mkdir dir="${dist}"/>
   <mkdir dir="${war_dir}"/>
 </target>

 <target name="compile" depends="init">
   <!-- Compile the java code from ${src} into ${build} -->
   <javac
     srcdir="${ejb.src}"
     destdir="${build}"
     classpath="${ejb.jar}"/>
   <javac
     srcdir="${top}/${src}"
     destdir="${build}"
     classpath="${servlet.jar}:${struts.jar}:${ejb.jar}"/>
 </target>

 <target name="dist" depends="compile">
   <!-- Put everything in a war file -->
   <war warfile="${war_file}" webxml="${web.xml}">
     <!-- include all JSPs in root level, and all .properties files anywhere -->
     <fileset dir="${top}/${src}">
       <include name="*.jsp"/>
       <include name="**/*.properties"/>
     </fileset>

     <!-- include all tag libraries in WEB-INF, and all .xml config files,
          but not web.xml (that's handled separately) -->
     <webinf dir="${webinf}">
       <include name="*.tld"/>
       <include name="*.xml"/>
       <exclude name="web.xml"/>
     </webinf>

     <!-- include all libraries in WEB-INF/lib (like struts.jar) -->
     <lib dir="${lib}"/>

     <!-- include all compiled classes -->
     <classes dir="${build}"/>
   </war>
 </target>

 <target name="deploy">
   <!-- Copy the war file to the JBoss deploy directory -->
   <copy file="${war_file}" todir="${deploy}"/>
 </target>

 <target name="all" depends="clean,dist,deploy"/>

</project>

code/Chapter6/Hello/HelloEJB/build.xml
<project name="HelloEJB" default="dist" basedir=".">

 <!-- set global properties for this build -->
 <property environment="env"/>
 <property name="top" value="."/>
 <property name="src" value="."/>
 <property name="build" value="build"/>
 <property name="dist" value="dist"/>
 <property name="jar_dir" value="${dist}/lib"/>
 <property name="jar_file" value="${jar_dir}/HelloEJB.jar"/>

 <property name="webinf" value="${top}/WEB-INF"/>
 <property name="web.xml" value="${webinf}/web.xml"/>
 <property name="classes" value="${webinf}/classes"/>
 <property name="lib" value="${top}/WEB-INF/lib"/>
 <property name="ejb.jar" value="${env.JBOSS_HOME}/lib/ext/jboss-j2ee.jar"/>
 <property name="deploy" value="${env.JBOSS_HOME}/deploy"/>

 <target name="clean">
   <!-- Delete our the ${build} and ${dist} directory trees -->
   <delete dir="${build}"/>
   <delete dir="${dist}"/>
   <delete dir="${jar_dir}"/>
 </target>

 <target name="init">
   <!-- Create the build directory structure used by compile and dist -->
   <mkdir dir="${build}"/>
   <mkdir dir="${dist}"/>
   <mkdir dir="${jar_dir}"/>
 </target>

 <target name="compile" depends="init">
   <!-- Compile the java code from ${src} into ${build} -->
   <javac
     srcdir="${top}/${src}"
     destdir="${build}"
     classpath="${ejb.jar}"/>
 </target>

 <target name="dist" depends="compile">
   <!-- Put everything in a jar file -->
   <jar jarfile="${jar_file}">
     <!-- include all compiled class files -->
     <fileset dir="${build}">
       <include name="**/*.class"/>
     </fileset>

     <!-- include ejb-jar.xml -->
     <fileset dir="${top}">
       <include name="META-INF/ejb-jar.xml"/>
     </fileset>
   </jar>
 </target>

 <target name="deploy">
   <!-- Copy the jar file to the JBoss deploy directory -->
   <copy file="${jar_file}" todir="${deploy}"/>
 </target>

 <target name="all" depends="clean,dist,deploy"/>

</project>

code/Chapter6/Hello/build.xml
<project name="Hello" default="all" basedir=".">

<target name="all">
  <ant dir="HelloClient" target="all"/>
  <ant dir="HelloEJB" target="all"/>
</target>

<target name="clean">
  <ant dir="HelloClient" target="clean"/>
  <ant dir="HelloEJB" target="clean"/>
</target>

</project>

  1. From the root Hello directory, do ant all to compile both the client code (in HelloClient) and the EJB code (in HelloEJB) and deploy both automatically. This "master" build script will call the individual build scripts we created for the client code and the EJB code. This is one of the most powerful features of Ant: being able to automatically tie together subprojects (which you can build and test separately) to build and deploy everything all at once.
  2. Go to http://localhost:8080/HelloClient/getEJB.do to test your application. This will be the most complicated "Hello World" program you will ever build.

StatefulHello: A Stateful Session Bean

A stateful session bean is like a stateless session bean, except that it includes accessor and mutator methods for its properties. Its implementation is very similar; the most important difference to remember is to declare the bean as Stateful in the ejb-jar.xml descriptor file.

Do this:

  1. Duplicate the entire Hello directory from the previous example. Rename it to StatefulHello. Your directory structure should look very familiar:
code/Chapter6/StatefulHello/HelloClient
HelloClient
  |
  +-- build.xml (*)
  |
  +-- index.jsp (*)
  |
  +-- WEB-INF
        |
        +-- web.xml (*)
        |
        +-- app.tld (*)
        |
        +-- struts-bean.tld (*)
        |
        +-- struts-form.tld (*)
        |
        +-- struts-html.tld (*)
        |
        +-- struts-logic.tld (*)
        |
        +-- struts-template.tld (*)
        |
        +-- struts.tld (*)
        |
        +-- struts-config.xml (*)
        |
        +-- classes
        |     |
        |     +-- com
        |           |
        |           +-- masslight
        |                 |
        |                 +-- beans
        |                 |     |
        |                 |     +-- HelloClientBean.java (*)
        |                 |
        |                 +-- actions
        |                       |
        |                       +-- GetBeanAction.java (*)
        |
        +-- lib
              |
              +-- struts.jar (*)

(*) denotes a file

  1. In the client code, everything is the same except two files: the Action class which calls the client bean, and the client bean that calls the EJB. Create the Action class.
code/Chapter6/StatefulHello/HelloClient/WEB-INF/classes/com/masslight/actions/GetBeanAction.java
package com.masslight.actions;

import java.util.Vector;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import java.util.Hashtable;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.util.*;

import com.masslight.beans.HelloClientBean;

public final class GetBeanAction extends Action {

    // The constructor method for this class
    public GetBeanAction() {
    }

    // This sets the list as a session bean
    public ActionForward perform(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response)
    throws IOException, ServletException {

        HttpSession session = request.getSession();

        HelloClientBean clientbean = new HelloClientBean();
        clientbean.startup();
        session.setAttribute("greeting", clientbean.sayGreeting("Hello from a stateful EJB!"));

        return (mapping.findForward("success"));
    }
}

  1. Create the client bean.
code/Chapter6/Hello/HelloClient/WEB-INF/classes/com/masslight/beans/HelloClientBean.java
package com.masslight.beans;

import javax.naming.*;
import java.util.Hashtable;
import javax.rmi.PortableRemoteObject;
import javax.ejb.*;

public class HelloClientBean
{
    com.masslight.HelloEJBClasses.Hello hello = null;
    com.masslight.HelloEJBClasses.HelloHome home = null;

    public HelloClientBean() {

    }

    /**
        This method does all the work. It creates an instance of the hello EJB on
     the EJB server, and calls its `sayHello()' method, then prints
     the result the returned String.
     */
    public void startup () {
        // Set up the naming provider; this may not always be necessary, depending
        // on how your Java system is configured.
        System.setProperty("java.naming.factory.initial",
                           "org.jnp.interfaces.NamingContextFactory");
        System.setProperty("java.naming.provider.url",
                           "localhost:1099");

        // A single `try' block is not an ideal way  to do exception handling, but it
        // is easier to read code without all the catch blocks
        try
        {
            // Get a naming context
            InitialContext jndiContext = new InitialContext();
            System.out.println("Got context \n");

            // Get a reference to the hello Bean
            Object ref  = jndiContext.lookup("Hello");
            System.out.println("Got reference \n");

            // Get a reference from this to the Bean's Home interface
            home = (com.masslight.HelloEJBClasses.HelloHome)
                PortableRemoteObject.narrow (ref, 
		  com.masslight.HelloEJBClasses.HelloHome.class);

            // Create an hello object from the Home interface
            hello = home.create();

        }   catch(Exception e)
        {
            System.out.println(e.toString());
        }
    }
    public String sayGreeting(String greeting) {
        String output = new String("");
        try {
            output += hello.sayHello(greeting);
        }  catch(Exception e)    {
            System.out.println(e.toString());
        }
        return(output);
    }
}

  1. You should already have the directory structure for the EJB, since you duplicated it along with the client code.
code/Chapter6/StatefulHello/HelloEJB
HelloEJB
  |
  +-- build.xml (*)
  |
  +-- META-INF
  |     |
  |     +-- ejb-jar.xml (*)
  |
  +-- com
        |
        +-- masslight
              |
              +-- HelloEJBClasses
                    |
                    +-- HelloBean.java (*)
                    |
                    +-- Hello.java (*)
                    |
                    +-- HelloHome.java (*)

(*) denotes a file

  1. The home interface is the same, but all other files need additional changes to accomodate the accessor and mutator methods and the statefulness of this EJB.
code/Chapter6/StatefulHello/HelloEJB/com/masslight/HelloEJBClasses/HelloHome.java
package com.masslight.HelloEJBClasses;
import java.io.Serializable;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;

/**
This interface defines the `Home' interface for the `Hello' EJB.
*/

public interface HelloHome extends EJBHome
{
    /**
    Creates an instance of the `HelloBean' class on the server, and returns a
     remote reference to an hello interface on the client.
     */
    Hello create() throws RemoteException, CreateException;
}

  1. Create the remote interface, which has additional setGreeting and getGreeting methods (compared with the stateless version in the previous example).
code/Chapter6/StatefulHello/HelloEJB/com/masslight/HelloEJBClasses/Hello.java
package com.masslight.HelloEJBClasses;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface Hello extends EJBObject
{
    public void setGreeting(String newGreeting) throws RemoteException;
    public String getGreeting() throws RemoteException;
    public String sayHello() throws RemoteException;
}

  1. Create the EJB class, which has additional getGreeting and setGreeting code, as well as an instance variable greeting. This is the state that this stateful EJB holds.
code/Chapter6/StatefulHello/HelloEJB/com/masslight/HelloEJBClasses/HelloBean.java
package com.masslight.HelloEJBClasses;
import java.rmi.RemoteException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;

public class HelloBean implements SessionBean
{
    String greeting;

    public void setGreeting(String newGreeting) {
        greeting = newGreeting;
    }
    public String getGreeting() {
        return (greeting);
    }
    public String sayHello()
    {
        return(getGreeting());
    }
    public HelloBean() {}
    public void ejbCreate() {}
    public void ejbRemove() {}
    public void ejbActivate() {}
    public void ejbPassivate() {}
    public void setSessionContext(SessionContext sc) {}
}

  1. Create the ejb.jar.xml descriptor file, which specifies that this EJB is stateful, and that it has the home interface, remote interface, and EJB class that we just defined in the previous 3 files.
code/Chapter6/StatefulHello/HelloEJB/META-INF/ejb-jar.xml
<?xml version="1.0" encoding="Cp1252"?>

<!DOCTYPE ejb-jar PUBLIC
  '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN'
  'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar>
  <display-name>Hello</display-name>
  <enterprise-beans>
    <session>
      <display-name>Hello</display-name>
      <ejb-name>Hello</ejb-name>
      <home>com.masslight.HelloEJBClasses.HelloHome</home>
      <remote>com.masslight.HelloEJBClasses.Hello</remote>
      <ejb-class>com.masslight.HelloEJBClasses.HelloBean</ejb-class>
      <session-type>Stateful</session-type>
      <transaction-type>Bean</transaction-type>
      <security-identity>
        <description></description>
        <use-caller-identity></use-caller-identity>
      </security-identity>
    </session>
  </enterprise-beans>
</ejb-jar>

  1. Create the build scripts for the client code, the EJB code, and the master script that ties them all together.
code/Chapter6/StatefulHello/HelloClient/build.xml
<project name="HelloClient" default="dist" basedir=".">

 <!-- set global properties for this build -->
 <property environment="env"/>
 <property name="top" value="."/>
 <property name="src" value="."/>
 <property name="build" value="build"/>
 <property name="dist" value="dist"/>
 <property name="war_dir" value="${dist}/lib"/>
 <property name="war_file" value="${war_dir}/HelloClient.war"/>

 <property name="webinf" value="${top}/WEB-INF"/>
 <property name="web.xml" value="${webinf}/web.xml"/>
 <property name="classes" value="${webinf}/classes"/>
 <property name="lib" value="${top}/WEB-INF/lib"/>
 <property name="struts.jar" value="${env.STRUTS_HOME}/lib/struts.jar"/>
 <property name="servlet.jar" value="${env.TOMCAT_HOME}/lib/servlet.jar"/>
 <property name="ejb.src" value="${top}/../HelloEJB"/>
 <property name="ejb.jar" value="${env.JBOSS_HOME}/lib/ext/jboss-j2ee.jar"/>
 <property name="deploy" value="${env.JBOSS_HOME}/deploy"/>

 <target name="clean">
   <!-- Delete our the ${build} and ${dist} directory trees -->
   <delete dir="${build}"/>
   <delete dir="${dist}"/>
   <delete dir="${war_dir}"/>
 </target>

 <target name="init">
   <!-- Create the build directory structure used by compile and dist -->
   <mkdir dir="${build}"/>
   <mkdir dir="${dist}"/>
   <mkdir dir="${war_dir}"/>
 </target>

 <target name="compile" depends="init">
   <!-- Compile the java code from ${src} into ${build} -->
   <javac
     srcdir="${ejb.src}"
     destdir="${build}"
     classpath="${ejb.jar}"/>
   <javac
     srcdir="${top}/${src}"
     destdir="${build}"
     classpath="${servlet.jar}:${struts.jar}:${ejb.jar}"/>
 </target>

 <target name="dist" depends="compile">
   <!-- Put everything in a war file -->
   <war warfile="${war_file}" webxml="${web.xml}">
     <!-- include all JSPs in root level, and all .properties files anywhere -->
     <fileset dir="${top}/${src}">
       <include name="*.jsp"/>
       <include name="**/*.properties"/>
     </fileset>

     <!-- include all tag libraries in WEB-INF, and all .xml config files,
          but not web.xml (that's handled separately) -->
     <webinf dir="${webinf}">
       <include name="*.tld"/>
       <include name="*.xml"/>
       <exclude name="web.xml"/>
     </webinf>

     <!-- include all libraries in WEB-INF/lib (like struts.jar) -->
     <lib dir="${lib}"/>

     <!-- include all compiled classes -->
     <classes dir="${build}"/>
   </war>
 </target>

 <target name="deploy">
   <!-- Copy the war file to the JBoss deploy directory -->
   <copy file="${war_file}" todir="${deploy}"/>
 </target>

 <target name="all" depends="clean,dist,deploy"/>

</project>

code/Chapter6/StatefulHello/HelloEJB/build.xml
<project name="HelloEJB" default="dist" basedir=".">

 <!-- set global properties for this build -->
 <property environment="env"/>
 <property name="top" value="."/>
 <property name="src" value="."/>
 <property name="build" value="build"/>
 <property name="dist" value="dist"/>
 <property name="jar_dir" value="${dist}/lib"/>
 <property name="jar_file" value="${jar_dir}/HelloEJB.jar"/>

 <property name="webinf" value="${top}/WEB-INF"/>
 <property name="web.xml" value="${webinf}/web.xml"/>
 <property name="classes" value="${webinf}/classes"/>
 <property name="lib" value="${top}/WEB-INF/lib"/>
 <property name="ejb.jar" value="${env.JBOSS_HOME}/lib/ext/jboss-j2ee.jar"/>
 <property name="deploy" value="${env.JBOSS_HOME}/deploy"/>

 <target name="clean">
   <!-- Delete our the ${build} and ${dist} directory trees -->
   <delete dir="${build}"/>
   <delete dir="${dist}"/>
   <delete dir="${jar_dir}"/>
 </target>

 <target name="init">
   <!-- Create the build directory structure used by compile and dist -->
   <mkdir dir="${build}"/>
   <mkdir dir="${dist}"/>
   <mkdir dir="${jar_dir}"/>
 </target>

 <target name="compile" depends="init">
   <!-- Compile the java code from ${src} into ${build} -->
   <javac
     srcdir="${top}/${src}"
     destdir="${build}"
     classpath="${ejb.jar}"/>
 </target>

 <target name="dist" depends="compile">
   <!-- Put everything in a jar file -->
   <jar jarfile="${jar_file}">
     <!-- include all compiled class files -->
     <fileset dir="${build}">
       <include name="**/*.class"/>
     </fileset>

     <!-- include ejb-jar.xml -->
     <fileset dir="${top}">
       <include name="META-INF/ejb-jar.xml"/>
     </fileset>
   </jar>
 </target>

 <target name="deploy">
   <!-- Copy the jar file to the JBoss deploy directory -->
   <copy file="${jar_file}" todir="${deploy}"/>
 </target>

 <target name="all" depends="clean,dist,deploy"/>

</project>

code/Chapter6/StatefulHello/build.xml
<project name="StatefulHello" default="all" basedir=".">

<target name="all">
  <ant dir="HelloClient" target="all"/>
  <ant dir="HelloEJB" target="all"/>
</target>

<target name="clean">
  <ant dir="HelloClient" target="clean"/>
  <ant dir="HelloEJB" target="clean"/>
</target>

</project>

  1. ant all from the root StatefulHello directory to compile and deploy both the client and EJB code.
  2. Go to http://localhost:8080/HelloClient/getEJB.do to test this application.

Entity Beans

Container-managed versus Bean-managed
An entity EJB is either bean-managed or container-managed. In a container-managed entity bean, the container handles the bean’s transactions with a database. In a bean-managed EJB, the bean class must handle the interactions with the database. The deployment descriptor for a container-managed bean includes information about the bean’s methods that a similar bean-managed EJB's deployment descriptor would not. Currently, container-managed beans are not well supported because EJB containers are not yet completely uniform, in regards to deployment of container-managed beans, and because not all containers manage container-managed beans very well. This means that bean-managed entity beans are the current de facto implementation of entity beans.

Implementing Entity Beans
Entity beans require a home interface, a remote interface, a bean class, and an XML deployment descriptor. All four parts need to follow certain guidelines in order to be correctly deployed by the EJB container. The home interface extends the javax.ejb.EJBHome interface and the remote interface extends the javax.ejb.EJBObject interface. The bean class needs to implement the javax.ejb.EntityBean interface.

Since an entity bean is meant to model a table in a database, it must have properties that correspond to the columns of the table. Below is an example of a table in a database and the corresponding properties of the table in a bean class.
 

SQL for example table created 
corresponding properties declared in the bean class
CREATE TABLE DEMO ( // no corresponding property
ID integer(8) not null, private long id;
COLUMN_ONE varchar(32) not null, private String columnOne;
primary key(ID)); // no corresponding property

The bean class must implement all the methods declared in the home, remote, and javax.ejb.EntityBean interfaces. Some of the methods that the bean class must implement do not have the exact same names as the methods that are declared in the interface. For example, create( arguments ) in the home interface is ejbCreate( same_arguments )in the bean class. Below is an outline of the methods required in the remote interface, the home interface, and the bean class of an entity bean. The EJB specification defines javax.ejb.EntityBean interface as the following, along with its superclass javax.ejb.EnterpriseBean:

interface EnterpriseBean

public interface EnterpriseBean extends java.io.Serializable {}

interface EntityBean

public interface EntityBean extends EnterpriseBean { 
public abstract void ejbActivate() throws java.rmi.RemoteException; 
public abstract void ejbLoad() throws java.rmi.RemoteException;
public abstract void ejbPassivate() throws java.rmi.RemoteException;
public abstract void ejbRemove() throws java.rmi.RemoteException;
public abstract void ejbStore() throws java.rmi.RemoteException;
public abstract void setEntityContext(EntityContext ctx) throws java.rmi.RemoteException;
public abstract void unsetEntityContext() throws java.rmi.RemoteException;
}

Below are tables detailing the necessary elements of the remote interface, the home interface, and the bean class of a bean-managed or container-managed entity bean.

remote interface extends javax.ejb.EJBObject

method name
returns
description 
exampleMethod(int GNPofFranceInBillionsOfPounds; int All methods declared in this interface are specific to a particular EJB. Each method declared here must also be implemented in the bean class. Note: this is an example method only used to show that methods declared in the remote interface must be implemented in the bean class. Not Required.

home interface extends javax.ejb.EJBHome

method name
Returns
description
create( ... ) throws java.rmi.RemoteException, javax.ejb.CreateException an instance of a stub associated with the remote interface of an instance of the bean class This should create a new row in the table - provided that a row with that primary key does not exist already. When the container receives this call, it creates an instance of the entity bean. Tthe create method can include parameters, as long as each create method declaration matches the implementation of an ejbCreate method in the bean class. There must be at least one create method, but there can be more than one. Required.
findByPrimaryKey (primary_key_type primary_key) throws java.rmi.RemoteException, javax.ejb.FinderException an instance of a stub associated with the remote interface of the instance of the bean class with the primary key primary_key This should return the row in the table with the primary key primary_key. It should also have a method in the bean called ejbFindByPrimaryKey. Required.
public void remove() throws FinderException, RemoteException void This method should remove the current row from the table.
 The declaration should correspond to the method in the bean called ejbRemove. Not required.
findAll() – or similar name a Collection of all the primary keys in the table Should return all the primary keys in a table - does not need to be called findAll. Not required.

bean class implements javax.ejb.EntityBean

method name
returns
description
ejbCreate( ... ) throws javax.ejb.CreateException primary_key_type This should add a row to the table in the database and set the properties of the bean to the values of the attributes of the row. There must be at least one ejbCreate method in the bean class, but there can be more than one. Required.
ejbPostCreate( ... ) void Alerts the bean class that the container has finished the call to an ejbCreate method. Required.
ejbFindByPrimaryKey(primary_key_type primary_key) throws javax.ejb.FinderException primary_key_type This should return the row with the primary key primary_key if that row exists, otherwise it should throw a FinderException. Required.
ejbFindAll() Collection This method should return all the primary keys in a table. It should correspond to the findAll method in the home interface, if one exists. Not required.
ejbLoad() void This method should load into the EJB a row in the table whose primary key is the value of the primary key attribute in the bean. Required.
ejbStore() void This method should update the contents of a row in the database whose primary key is the value of the primary key attribute in the bean. Required.
ejbRemove() void The container calls this method before it removes the bean from itself. This method should remove the row in the table that corresponds with the state of the bean. Required.
ejbActivate() void The container calls this method after it brings the EJB back from secondary memory. Required.
ejbPassivate() void The container calls this method before it moves the EJB to secondary memory. Required.
setEntityContext(EntityContext ctx) void The container calls this method after the bean has been created but before any of the ejbCreate methods have been called. The bean is responsible for storing the entity context object. Required.
unsetEntityContext() void The container calls this method before it destroys the EJB. Required.
exampleMethod(int GNPofFranceInBillionsOfPounds) int This method would need to be in the bean class if it is declared in the remote interface. Note: this is an example method demonstrating the need to implement methods declared in the remote interface. Not required.

Important interfaces and classes of the javax.ejb package

javax.ejb.EJBHome
The home interface extends the EJBHome interface.

interface EJBHome

public interface EJBHome extends java.rmi.Remote { 
public abstract EJBMetaData getEJBMetaData() throws java.rmi.RemoteException; 
public abstract void remove(Object primaryKey) throws java.rmi.RemoteException, RemoveException;
public abstract void remove(Handle handle) throws java.rmi.RemoteException, RemoveException;
}

interface javax.ejb.EJBHome

method declaration
description
public abstract EJBMetaData getEJBMetaData() throws java.rmi.RemoteException; will return the EJBMetaData object, which can be used for introspection by EJB developer tools
public abstract void remove(Object primaryKey) throws java.rmi.RemoteException, RemoveException; will remove the client's reference to the EJB in the container
public abstract void remove(Handle handle) throws java.rmi.RemoteException, RemoveException; will remove the client's reference to the EJB in the container

javax.ejb.EJBObject
The remote interface extends the EJBObject interface.

interface EJBObject

public interface EJBObject extends java.rmi.Remote { 
public abstract EJBHome getEJBHome() throws java.rmi.RemoteException;
public abstract Handle getHandle() throws java.rmi.RemoteException;
public abstract void getPrimaryKey() throws java.rmi.RemoteException;
public abstract boolean isIdentical(EJBObject obj) throws java.rmi.RemoteException;
public abstract void remove() throws java.rmi.RemoteException, RemoveException;
}

interface javax.ejb.EJBObject

method declaration
description
public abstract EJBHome getEJBHome() throws java.rmi.RemoteException; will return a stub to the corresponding home interface 
public abstract Handle getHandle() throws java.rmi.RemoteException; will return a handle to the remote interface
public abstract void getPrimaryKey() throws java.rmi.RemoteException; will return the primary key of EJB, if the EJB is an entity bean
public abstract boolean isIdentical(EJBObject obj) throws java.rmi.RemoteException; will compare two remote stubs to see if they are the same
public abstract void remove() throws java.rmi.RemoteException, RemoveException; will remove client's reference to the remote interface

javax.ejb.EJBContext

interface EJBContext

public interface EJBContext extends java.rmi.Remote { 
public abstract java.security.Identity getCallerIdentity();
public abstract EJBHome getEJBHome();
public abstract java.util.Properties getEnvironment();
public abstract boolean getRollbackOnly();
public abstract javax.jts.UserTransaction getUserTransaction() throws java.lang.IllegalStateException;
public abstract boolean IsCallerInRole(java.security.identity role);
public abstract void setRollbackOnly();
}

interface javax.ejb.EJBContext

method declaration
description
public abstract java.security.Identity getCallerIdentity(); will allow the caller of the method to learn the identity of the caller of the EJB
public abstract EJBHome getEJBHome(); will return a handle to the remote interface
public abstract java.util.Properties getEnvironment(); will return a list of properties that the EJB container sends to its objects
public abstract boolean getRollbackOnly(); will tell the EJB object if it is executing within a transaction that has been rolled back
public abstract javax.jts.UserTransaction getUserTransaction() throws java.lang.IllegalStateException; will return the container's implementation of the javax.transaction.UserTransaction interface
public abstract boolean IsCallerInRole(java.security.identity role); will check to see if the caller of the EJB is within the caller's proper security role
public abstract void setRollbackOnly(); sets the current transaction, if there is a transaction, to be rolled back

javax.ejb.Handle

interface Handle

public interface Handle{ 
public abstract EJBObject getEJBObject() throws java.rmi.RemoteException;
}

interface javax.ejb.Handle

public abstract EJBObject getEJBObject() throws java.rmi.RemoteException; will return a remote reference to the EJBObject that it is representing

javax.ejb.EntityContext

interface EntityContext

public interface EntityContext extends javax.ejb.EJBContext{ 
public abstract EJBObject getEJBObject() throws java.lang.IllegalStateException;
public abstract Object getPrimaryKey() throws java.lang.IllegalStateException;
}

interface javax.ejb.EntityContext

method declaration
description
public abstract EJBObject getEJBObject() throws java.lang.IllegalStateException; will return the stub for the remote interface
public abstract Object getPrimaryKey() throws java.lang.IllegalStateException; will return the primary key of the row associated with the EJB

javax.ejb.SessionContext

interface SessionContext

public interface SessionContext extends javax.ejb.EJBContext { 
public abstract EJBObject getEJBObject() throws java.lang.IllegalStateException;
}

interface javax.ejb.SessionContext

method declaration
description
public abstract EJBObject getEJBObject() throws java.lang.IllegalStateException; will return the stub for the remote interface

javax.ejb.EJBMetaData

This interface can be used for introspection by an EJB development tool

interface EJBMetaData

public interface EJBMetaData { 
public abstract EJBHome getEJBHome();
public abstract Class getHomeInterfaceClass();
public abstract Class getPrimaryKeyClass();
public abstract Class getRemoteInterfaceClass();
public abstract boolean isSession();
}

interface javax.ejb.EJBMetaData

method declaration
description
public abstract EJBHome getEJBHome(); will return the stub for the home interface
public abstract Class getHomeInterfaceClass(); will return the home interface class
public abstract Class getPrimaryKeyClass(); will return the primary key class, if the EJB is an entity bean
public abstract Class getRemoteInterfaceClass(); will return the remote interface class
public abstract boolean isSession(); will return whether the EJB is a session bean or not

Accessing Databases

JDBC is the industry standard way to access databases from Java applications. There is a JDBC driver for virtually every database you can imagine; many databases have more than one available, which you can choose between based on the needs of your application.

Configuring JBoss to use an OpenBase database

First, add the Openbase JDBC driver (OpenBase.jar) to the lib/ext directory under JBoss.

Second, we must tell JBoss some more information about this driver. Open the jboss.jcml configuration file and find the JDBC configuration section. Add the class for the OpenBase JDBC driver (com.openbase.jdbc.ObDriver) to the list of classes in the JDBC section:


  <mbean code="org.jboss.jdbc.JdbcProvider" name="DefaultDomain:service=JdbcProvider">
     <attribute name="Drivers">org.hsqldb.jdbcDriver,com.openbase.jdbc.ObDriver</attribute>
  </mbean>

Coding with Databases

Accessing a database consists of four main steps.

  1. Create a database connection, using the appropriate JDBC driver
  2. Create a SQL statement
  3. Send the SQL statement to the database to select or update data
  4. Close the database connection when it is no longer needed

Loading a JDBC Driver


String driverClass = "com.openbase.jdbc.ObDriver";
Class.forName(driverClass);

Connecting To The Database


try {
    String database = new String("jdbc:openbase://127.0.0.1/phone");
    String username = new String("admin");
    String password = new String("");
    java.sql.Connection connection = DriverManager.getConnection(database, username, password);
} catch (SQLException e) {
    System.out.println("AN SQL Exception occurred while getting a connection");
}

Fetching Data


String sqlString = "select FIRST_NAME, LAST_NAME, PHONE_NUMBER " +
  "from PHONEPAGES where PHONE_ID=";
sqlString += $PHONE_ID_TO_FETCH$;
Statement s = connection.createStatement();
s.executeQuery(sqlString);

Accessing Fetched Data


ResultSet rs = s.getResultSet();
if (rs.next()) {
  this.firstName = rs.getString(1);
  this.lastName = rs.getString(2);
  this.phoneNumber = rs.getString(3);
}

Updating Data


try {
    String sqlString = "update PHONEPAGES set FIRST_NAME = '";
    sqlString += tempFirstName;
    sqlString += "'" + ", LAST_NAME = '";
    sqlString += tempLastName;
    sqlString += "'" + ", PHONE_NUMBER = '";
    sqlString += tempPhoneNumber;
    sqlString += "'";
    sqlString += " where PHONE_ID = ";
    sqlString += tempPhoneNumber;
    connection.setAutoCommit(false);
    Statement s = connection.createStatement();
    s.executeUpdate(sqlString);
    connection.commit();
    s.close();
} catch (SQLException e) {
    connection.rollback();
}

Inserting New Data


try {
    String sqlString = "insert into PHONEPAGES " +
        "(PHONE_ID, FIRST_NAME, LAST_NAME, PHONE_NUMBER) values (";
    sqlString += tempPhoneId;
    sqlString += ", '" + tempFirstName;
    sqlString += "', '" + tempLastName;
    sqlString += "', '" + tempPhoneNumber;
    sqlString += "')";
    connection.setAutoCommit(false);
    Statement s = connection.createStatement();
    s.executeUpdate(sqlString);
    connection.commit();
    s.close();
} catch (SQLException e) {
    connection.rollback();
}

Deleting Data


try {
    String sqlString = "delete from PHONEPAGES where PHONE_ID = ";
    sqlString += tempPhoneId;
    connection.setAutoCommit(false);
    Statement s = connection.createStatement();
    s.executeUpdate(sqlString);
    connection.commit();
    s.close();
} catch (SQLException e) {
    connection.rollback();
}

Closing The Connection


try {
    connection.close();
} catch (SQLException e) {
    System.out.println("An SQL Exception occurred while closing the connection");
}

Phone: A Bean-managed Entity Bean

Do this:

  1. Create the directory structure. The root directory should be Phone. Within that, we will have two separate directories, PhoneClient and PhoneEJB. PhoneClient looks just like the rest of the web applications we've been building, including Hello and StatefulHello. It uses the Struts framework and has the directory hierarchies that should look very familiar by now.
  2. Copy the Struts framework into WEB-INF.
code/Chapter6/Phone/PhoneClient
PhoneClient
  |
  +-- index.jsp (*)
  |
  +-- build.xml (*)
  |
  +-- WEB-INF
        |
        +-- web.xml (*)
        |
        +-- app.tld (*)
        |
        +-- struts-bean.tld (*)
        |
        +-- struts-form.tld (*)
        |
        +-- struts-html.tld (*)
        |
        +-- struts-logic.tld (*)
        |
        +-- struts-template.tld (*)
        |
        +-- struts.tld (*)
        |
        +-- struts-config.xml (*)
        |
        +-- classes
        |     |
        |     +-- com
        |           |
        |           +-- masslight
        |                 |
        |                 +-- beans
        |                 |     |
        |                 |     +-- PhoneClientBean.java (*)
        |                 |
        |                 +-- actions
        |                       |
        |                       +-- GetBeanAction.java (*)
        |
        +-- lib
              |
              +-- struts.jar (*)

(*) denotes a file

  1. Create the JSP, Action class, and client bean.
code/Chapter6/Phone/PhoneClient/index.jsp
<%@ page language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %> 
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %> 
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %> 
<html:html>
  <head> 
    <title> 
      Entity Bean 
    </title> 
    <html:base/> 
  </head> 
  <body> 

    <logic:present name="greeting">
      <bean:write name="greeting"/>
    </logic:present>
    <br/>
    <logic:present name="addRow">
      <bean:write name="addRow"/>
    </logic:present>
    <br/>
    <logic:present name="fetchRow">
      <bean:write name="fetchRow"/>
    </logic:present>
    <br/>
    <logic:present name="printRow">
      <bean:write name="printRow"/> 
    </logic:present>
    <br/>
   </body> 
</html:html>

code/Chapter6/Phone/PhoneClient/WEB-INF/classes/com/masslight/actions/GetBeanAction.java
package com.masslight.actions;
import java.util.Vector;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Locale;
import java.util.Hashtable;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.util.*;

import com.masslight.beans.PhoneClientBean;

public final class GetBeanAction extends Action {

    // The constructor method for this class
    public GetBeanAction() {
    }

    // this is called when the .../getEJB.do URL is requested from the browser
    public ActionForward perform(ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response)
    throws IOException, ServletException {

        HttpSession session = request.getSession();

        PhoneClientBean clientbean = new PhoneClientBean();

        session.setAttribute("greeting", clientbean.startup("Hello"));
        session.setAttribute("addRow", 
	  clientbean.addRow(new Integer("440"), "Joe", "Orange", "1231112222"));
        session.setAttribute("fetchRow", clientbean.fetchRow(new Integer("440")));
        session.setAttribute("printRow", clientbean.printRow());

        return (mapping.findForward("success"));
    }
}

code/Chapter6/Phone/PhoneClient/WEB-INF/classes/com/masslight/beans/PhoneClientBean.java
package com.masslight.beans;

import com.masslight.PhoneNumberEJBClasses.*;
import javax.naming.*;
import java.util.Hashtable;
import javax.rmi.PortableRemoteObject;
import javax.ejb.*;

public class PhoneClientBean
{

    String jndi_name = new String("Phone");

    private PhoneNumber phone = null;
    private PhoneNumberHome home = null;

    public String startup (String greeting) {
        System.setProperty("java.naming.factory.initial",
                           "org.jnp.interfaces.NamingContextFactory");
        System.setProperty("java.naming.provider.url",
                           "localhost:1099");

        String output = new String("");
 

        try
        {
            // Get a naming context
            InitialContext jndiContext = new InitialContext();
            System.out.println("Got context \n");

            // Get a reference to the PhoneNumber Bean
            Object ref  = jndiContext.lookup(jndi_name);
            System.out.println("Got reference \n");

            // Get a reference from this to the Bean's Home interface
            this.home = ( PhoneNumberHome)
                PortableRemoteObject.narrow (ref, PhoneNumberHome.class);
            System.out.println("Got home \n");

        }
        catch(Exception e)
        {
            System.out.println(e.toString());
        }
        return(greeting);
    }

    public String addRow(Integer phoneId, String firstName, 
                         String lastName, String phoneNumber) {
        try {
            this.phone = this.home.create(phoneId, firstName, lastName, phoneNumber);
        }
        catch (Exception e) {
            e.printStackTrace();
            return ("error - row could not be added");
        }
        return ("row added");
    }

    public String fetchRow(Integer tempPhoneId) {
        try {
            this.phone = this.home.findByPrimaryKey(tempPhoneId);
        }
        catch (Exception e) {
            System.out.println("Error in fetching row");
            e.printStackTrace();
            return ("error - row could not be fetched");
        }
        return ("row fetched");
    }

    public String printRow () {
        try {

            String printString = new String("");
            printString += this.phone.getPhoneId() + ", " +
		           this.phone.getFirstName() + ", " +
                           this.phone.getLastName() + ", " + 
                           this.phone.getPhoneNumber();
            return (printString);
        }
        catch (Exception e) {
            System.out.println("Error in printing row");
            e.printStackTrace();
            return ("error - row could not be printed");
        }
    }
}

  1. Create the EJB directory structure.
code/Chapter6/Phone/PhoneEJB
PhoneEJB
  |
  +-- build.xml (*)
  |
  +-- META-INF
  |     |
  |     +-- ejb-jar.xml (*)
  |
  +-- com
        |
        +-- masslight
              |
              +-- PhoneNumberEJBClasses
                    |
                    +-- PhoneNumber.java (*)
                    |
                    +-- PhoneNumberHome.java (*)
                    |
                    +-- PhoneNumberBean.java (*)

(*) denotes a file

  1. Create the home interface, remote interface, and EJB bean classes.
code/Chapter6/Phone/PhoneEJB/com/masslight/PhoneNumberEJBClasses/PhoneNumberHome.java
package com.masslight.PhoneNumberEJBClasses;
import java.util.Collection;
import java.rmi.RemoteException;
import javax.ejb.*;

public interface PhoneNumberHome extends EJBHome {

  public PhoneNumber create(Integer phoneId, 
			    String firstName, 
			    String lastName, 
			    String phoneNumber) 
      throws RemoteException, CreateException;

  public PhoneNumber findByPrimaryKey(Integer phoneId) 
      throws FinderException, RemoteException; 
}

code/Chapter6/Phone/PhoneEJB/com/masslight/PhoneNumberEJBClasses/PhoneNumber.java
package com.masslight.PhoneNumberEJBClasses;
import javax.ejb.EJBObject;
import java.rmi.RemoteException;

public interface PhoneNumber extends EJBObject {
    public Integer getPhoneId() throws RemoteException;
    public String getFirstName() throws RemoteException;
    public String getLastName() throws RemoteException;
    public String getPhoneNumber() throws RemoteException; 
}

code/Chapter6/Phone/PhoneEJB/com/masslight/PhoneNumberEJBClasses/PhoneNumberBean.java
package com.masslight.PhoneNumberEJBClasses;
import java.sql.*;
import java.util.Enumeration;
import java.util.Vector;
import java.util.Collection;
import java.util.Iterator;
import javax.ejb.*;

public class PhoneNumberBean implements EntityBean {

    private EntityContext context = null;

    private Connection connection = null;

    private Integer phoneId = new Integer(0);
    private String firstName = new String();
    private String lastName = new String();
    private String phoneNumber = new String();

    /////////////////////////////////////////////////////////////////////////////

    public void makeConnection() {
        System.out.println("in makeConnection");
        try {
            Class.forName("com.openbase.jdbc.ObDriver");
            try {
                String database = new String("jdbc:openbase://127.0.0.1/phone");
                String username = new String("admin");
                String password = new String("");
                connection = DriverManager.getConnection(database, username, password);
            } catch (SQLException e) {
                System.out.println("AN SQL Exception occurred while getting a connection");
            }
        } catch (ClassNotFoundException cnfe) {
            System.out.println("Failed to load JDBC drivers.");
        }
        System.out.println("leaving makeConnection");
    }

    /////////////////////////////////////////////////////////////////////////////

    public void closeConnection() {
        System.out.println("in closeConnection");
        try {
            connection.close();
        } catch (SQLException e) {
            System.out.println("An SQL Exception occurred while closing the connection");
        }
        System.out.println("leaving closeConnection");
    }

    /////////////////////////////////////////////////////////////////////////////

    public Integer ejbCreate(Integer tempPhoneId, 
			     String tempFirstName, 
			     String tempLastName, 
			     String tempPhoneNumber) throws CreateException {
        System.out.println("in ejbCreate");
        try {
            insertRow(tempPhoneId, tempFirstName, tempLastName, tempPhoneNumber);
        } catch (Exception ex) {
            throw new EJBException("ejbCreate: " + ex.getMessage());
        }
        this.phoneId = tempPhoneId;
        this.firstName = tempFirstName;
        this.lastName = tempLastName;
        this.phoneNumber = tempPhoneNumber;
        System.out.println("leaving ejbCreate with " + tempPhoneId);
        return tempPhoneId;
    }

    public void ejbPostCreate(Integer tempPhoneId, 
			      String tempFirstName, 
			      String tempLastName, 
			      String tempPhoneNumber) {
        System.out.println("in ejbPostCreate");
        System.out.println("leaving ejbPostCreate");
    }

    private void insertRow(Integer tempPhoneId, 
			   String tempFirstName, 
			   String tempLastName, 
			   String tempPhoneNumber) throws Exception {

        System.out.println("in insertRow");

        String sqlString = "insert into PHONEPAGES " +
	    "(PHONE_ID, FIRST_NAME, LAST_NAME, PHONE_NUMBER) values (";
        sqlString += tempPhoneId;
        sqlString += ", '" + tempFirstName;
        sqlString += "', '" + tempLastName;
        sqlString += "', '" + tempPhoneNumber;
        sqlString += "')";

        System.out.println("using sql:  " + sqlString);

        try {
            makeConnection();
            connection.setAutoCommit(false);
            Statement s = connection.createStatement();
            s.executeUpdate(sqlString);
            connection.commit();
            s.close();
            closeConnection();
        } catch (SQLException e) {
            try {
                connection.rollback();
            } catch (SQLException e2) {
                throw new Exception("Big problems, error committing, error rolling back!!!");
            }
            throw new Exception("Error inserting Phone into database");
        }

        System.out.println("leaving insertRow");

    }

    /////////////////////////////////////////////////////////////////////////////

    public void ejbRemove() {

        System.out.println("in ejbRemove");

        try {
            deleteRow(this.phoneId);
        } catch (Exception ex) {
            throw new EJBException("ejbRemove: " + ex.getMessage());
        }
        System.out.println("leaving ejbRemove");
    }

    private void deleteRow(Integer tempPhoneId) {

        System.out.println("in deleteRow");

        String sqlString = "delete from PHONEPAGES where PHONE_ID = ";
        sqlString += tempPhoneId;

        System.out.println("using sql:  " + sqlString);

        try {
            makeConnection();
            connection.setAutoCommit(false);
            Statement s = connection.createStatement();
            s.executeUpdate(sqlString);
            connection.commit();
            s.close();
            closeConnection();
        } catch (SQLException e) {
            try {
                connection.rollback();
            } catch (SQLException e2) {
                System.out.println("Big problems, error committing, error rolling back!!!");
            }
            System.out.println("Error deleting Phone from database");
        }
        System.out.println("leaving deleteRow");
    }

    /////////////////////////////////////////////////////////////////////////////

    public void ejbLoad() {
        System.out.println("in ejbLoad");
        setPhoneId((Integer)(context.getPrimaryKey()));
        try {
            loadRow(getPhoneId());
        } catch (Exception ex) {
            throw new EJBException("ejbLoad: " + ex.getMessage());
        }
        System.out.println("leaving ejbLoad");
    }

    private void loadRow(Integer tempPhoneId) throws Exception{
        System.out.println("in loadRow");
        try {
            makeConnection();
            String sqlString = "select FIRST_NAME, LAST_NAME, PHONE_NUMBER " +
		"from PHONEPAGES where PHONE_ID=";
            sqlString += tempPhoneId;
            Statement s = connection.createStatement();
            s.executeQuery(sqlString);
            ResultSet rs = s.getResultSet();
            if (rs.next()) {
                this.firstName = rs.getString(1);
                this.lastName = rs.getString(2);
                this.phoneNumber = rs.getString(3);
            }
            s.close();
            closeConnection();
        } catch (Exception e) {
            throw new Exception("Error fetching Phone from database");
        }
        System.out.println("leaving loadRow");
    }

    /////////////////////////////////////////////////////////////////////////////

    public void ejbStore() {

        System.out.println("in ejbStore");

        setPhoneId((Integer)(context.getPrimaryKey()));

        try {
            storeRow(getPhoneId(), getFirstName(), getLastName(), getPhoneNumber());
        } catch (Exception ex) {
            throw new EJBException("ejbLoad: " + ex.getMessage());
        }
        System.out.println("leaving ejbStore");
    }

    private void storeRow(Integer tempPhoneId, 
			  String tempFirstName, 
			  String tempLastName, String tempPhoneNumber) {

        System.out.println("in storeRow");

        String sqlString = "update PHONEPAGES set FIRST_NAME = '";
        sqlString += tempFirstName;
        sqlString += "'" + ", LAST_NAME = '";
        sqlString += tempLastName;
        sqlString += "'" + ", PHONE_NUMBER = '";
        sqlString += tempPhoneNumber;
        sqlString += "'";
        sqlString += " where PHONE_ID = ";
        sqlString += tempPhoneNumber;

        try {
            makeConnection();
            connection.setAutoCommit(false);
            Statement s = connection.createStatement();
            s.executeUpdate(sqlString);
            connection.commit();
            s.close();
            closeConnection();
        } catch (SQLException e) {
            try {
                connection.rollback();
            } catch (SQLException e2) {
                System.out.println("Big problems, error committing, error rolling back!!!");
                e2.printStackTrace();
            }
            System.out.println("Error updating Phone in database");
        }
        System.out.println("leaving storeRow");
    }

    public Integer ejbFindByPrimaryKey(Integer tempPhoneID) throws FinderException {

        System.out.println("in ejbFindByPrimaryKey");
	System.out.println(tempPhoneID);

        boolean result = false;

        try {
            result = selectByPrimaryKey(tempPhoneID);
        } catch (Exception ex) {
            throw new EJBException("ejbFindByPrimaryKey: " + ex.getMessage());
        }

        if (result) {
            System.out.println("leaving ejbFindByPrimaryKey with " + tempPhoneID);
            return tempPhoneID;
        } else {
            throw new ObjectNotFoundException("Row for id " + tempPhoneID + " not found.");
        }
    }

    private boolean selectByPrimaryKey(Integer tempPhoneID) throws Exception{

        System.out.println("in selectByPrimaryKey");

        try {
            makeConnection();
            String sqlString = "select FIRST_NAME, LAST_NAME, PHONE_NUMBER " +
		"from PHONEPAGES where PHONE_ID=" + tempPhoneID;
            Statement s = connection.createStatement();
            s.executeQuery(sqlString);
            ResultSet rs = s.getResultSet();
            if (rs.next()) {
	        this.firstName = rs.getString(1);
                this.lastName = rs.getString(2);
                this.phoneNumber = rs.getString(3);
                this.phoneId = tempPhoneID;
            }
            s.close();
            closeConnection();
        } catch (SQLException e) {
            throw new Exception("Error fetching Phone from database");
        }
        System.out.println("leaving selectByPrimaryKey with " + tempPhoneID);

        return true;
    }

    public void setEntityContext(EntityContext context) {
        System.out.println("in setEntityContext");
        this.context = context;
        System.out.println("leaving setEntityContext");
    }

    public void unsetEntityContext() {
        System.out.println("in unsetEntityContext");
        this.context = null;
        System.out.println("leaving unsetEntityContext");
    }

    public void ejbActivate() {
        System.out.println("in ejbActivate");
        System.out.println("leaving ejbActivate");
    }

    public void ejbPassivate() {
        System.out.println("in ejbPassivate");
        System.out.println("leaving ejbPassivate");
    }

    public Integer getPhoneId() {
        System.out.println("in getPhoneId");
        System.out.println("leaving getPhoneId with " + this.phoneId);
        return this.phoneId;
    }
    public String getFirstName() {
        System.out.println("in getFirstName");
        System.out.println("leaving getName with " + this.firstName);
        return this.firstName;
    }
    public String getLastName() {
        System.out.println("in getLastName");
        System.out.println("leaving getPhoneId with " + this.lastName);
        return this.lastName;
    }
    public String getPhoneNumber() {
        System.out.println("in getPhoneNumbers");
        System.out.println("leaving getName with " + this.phoneNumber);
        return this.phoneNumber;
    }

    public void setPhoneId(Integer value) {
        this.phoneId = value;
    }
    public void setFirstName(String value) {
        this.firstName = new String(value);
    }
    public void setLastName(String value) {
        this.lastName = value;
    }

    public void setPhoneNumber(String value) {
        this.phoneNumber = new String(value);
    } 
}

  1. Create the ejb-jar.xml file.
code/Chapter6/Phone/PhoneEJB/META-INF/ejb-jar.xml
<?xml version="1.0" encoding="Cp1252"?> 
<!DOCTYPE ejb-jar PUBLIC 
  '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN' 
  'http://java.sun.com/dtd/ejb-jar_2_0.dtd'>

<ejb-jar> 
  <display-name>Phone</display-name> 
  <enterprise-beans> 
    <entity> 
      <display-name>Phone</display-name> 
      <ejb-name>Phone</ejb-name> 
      <home>com.masslight.PhoneNumberEJBClasses.PhoneNumberHome</home> 
      <remote>com.masslight.PhoneNumberEJBClasses.PhoneNumber</remote> 
      <ejb-class>com.masslight.PhoneNumberEJBClasses.PhoneNumberBean</ejb-class> 
      <persistence-type>Bean</persistence-type> 
      <prim-key-class>java.lang.Integer</prim-key-class> 
      <reentrant>False</reentrant> 
      <security-identity> 
        <description></description> 
        <use-caller-identity></use-caller-identity> 
      </security-identity> 
    </entity> 
  </enterprise-beans> 
  <assembly-descriptor> 
    <container-transaction>
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Home</method-intf> 
        <method-name>remove</method-name> 
        <method-params> 
          <method-param>java.lang.Object</method-param> 
        </method-params> 
      </method> 
      <trans-attribute>Required</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Home</method-intf> 
        <method-name>findByPrimaryKey</method-name> 
        <method-params> 
          <method-param>java.lang.Integer</method-param> 
        </method-params> 
      </method> 
      <trans-attribute>Required</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Remote</method-intf> 
        <method-name>getPhoneNumber</method-name> 
        <method-params /> 
      </method> 
      <trans-attribute>NotSupported</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Home</method-intf> 
        <method-name>remove</method-name> 
        <method-params> 
          <method-param>javax.ejb.Handle</method-param> 
        </method-params> 
      </method> 
      <trans-attribute>Required</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Home</method-intf> 
        <method-name>create</method-name> 
        <method-params> 
          <method-param>java.lang.Integer</method-param> 
          <method-param>java.lang.String</method-param> 
          <method-param>java.lang.String</method-param> 
          <method-param>java.lang.String</method-param> 
        </method-params> 
      </method> 
      <trans-attribute>Required</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Remote</method-intf> 
        <method-name>getLastName</method-name> 
        <method-params /> 
      </method> 
      <trans-attribute>NotSupported</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Remote</method-intf> 
        <method-name>getPhoneId</method-name> 
        <method-params /> 
      </method> 
      <trans-attribute>NotSupported</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Remote</method-intf> 
        <method-name>getFirstName</method-name> 
        <method-params /> 
      </method> 
      <trans-attribute>NotSupported</trans-attribute> 
    </container-transaction> 
    <container-transaction> 
      <method> 
        <ejb-name>Phone</ejb-name> 
        <method-intf>Remote</method-intf> 
        <method-name>remove</method-name> 
        <method-params /> 
      </method> 
      <trans-attribute>Required</trans-attribute> 
    </container-transaction> 
  </assembly-descriptor> 
</ejb-jar>

  1. Create the build scripts: one in PhoneClient for the client code, one in PhoneEJB for the EJB code, and one in Phone that ties them both together.
code/Chapter6/Phone/PhoneClient/build.xml
<project name="PhoneClient" default="dist" basedir=".">

 <!-- set global properties for this build -->
 <property environment="env"/>
 <property name="top" value="."/>
 <property name="src" value="."/>
 <property name="build" value="build"/>
 <property name="dist" value="dist"/>
 <property name="war_dir" value="${dist}/lib"/>
 <property name="war_file" value="${war_dir}/PhoneClient.war"/>

 <property name="webinf" value="${top}/WEB-INF"/>
 <property name="web.xml" value="${webinf}/web.xml"/>
 <property name="classes" value="${webinf}/classes"/>
 <property name="lib" value="${top}/WEB-INF/lib"/>
 <property name="struts.jar" value="${env.STRUTS_HOME}/lib/struts.jar"/>
 <property name="servlet.jar" value="${env.TOMCAT_HOME}/lib/servlet.jar"/>
 <property name="ejb.src" value="${top}/../PhoneEJB"/>
 <property name="ejb.jar" value="${env.JBOSS_HOME}/lib/ext/jboss-j2ee.jar"/>
 <property name="deploy" value="${env.JBOSS_HOME}/deploy"/>

 <target name="clean">
   <!-- Delete our the ${build} and ${dist} directory trees -->
   <delete dir="${build}"/>
   <delete dir="${dist}"/>
   <delete dir="${war_dir}"/>
 </target>

 <target name="init">
   <!-- Create the build directory structure used by compile and dist -->
   <mkdir dir="${build}"/>
   <mkdir dir="${dist}"/>
   <mkdir dir="${war_dir}"/>
 </target>

 <target name="compile" depends="init">
   <!-- Compile the java code from ${src} into ${build} -->
   <javac
     srcdir="${ejb.src}"
     destdir="${build}"
     classpath="${ejb.jar}"/>
   <javac
     srcdir="${top}/${src}"
     destdir="${build}"
     classpath="${servlet.jar}:${struts.jar}:${ejb.jar}"/>
 </target>

 <target name="dist" depends="compile">
   <!-- Put everything in a war file -->
   <war warfile="${war_file}" webxml="${web.xml}">
     <!-- include all JSPs in root level, and all .properties files anywhere -->
     <fileset dir="${top}/${src}">
       <include name="*.jsp"/>
       <include name="**/*.properties"/>
     </fileset>

     <!-- include all tag libraries in WEB-INF, and all .xml config files,
          but not web.xml (that's handled separately) -->
     <webinf dir="${webinf}">
       <include name="*.tld"/>
       <include name="*.xml"/>
       <exclude name="web.xml"/>
     </webinf>

     <!-- include all libraries in WEB-INF/lib (like struts.jar) -->
     <lib dir="${lib}"/>

     <!-- include all compiled classes -->
     <classes dir="${build}"/>
   </war>
 </target>

 <target name="deploy">
   <!-- Copy the war file to the JBoss deploy directory -->
   <copy file="${war_file}" todir="${deploy}"/>
 </target>

 <target name="all" depends="clean,dist,deploy"/>

</project>

code/Chapter6/