t e m p o r a l 
 d o o r w a y 

A Basic Enterprise Java Bean

 

Introduction

Enterprise Java Bean technology makes it very easy to create distributed programs, and JBuilder 4 makes it almost as easy to create EJBs as it is to create conventional JavaBeans.

The EJB model is well described in the Enterprise Application Developer's Guide that comes with JBuilder 4, as are the procedures. However, this offers a good beginner's guide.

The Application Server and EJB Container

Two major pieces are required for a successful EJB deployment - the EJB Jar and the Application Server EJB Container. This article covers creating the former, but it is important to understand the basics of the second.

The Application Server is an executable that resides somewhere on the network. Its function is to provide a place for the EJB to run on the remote CPU.

Application Servers serve more functions than just housing EJBs. They offer JNDI (Java Naming and Directory Interface) services, Servlet execution, and various other capabilities. Some also support CORBA capabilities and act as or interface with ORBs (Object Request Brokers). But for EJBs, what counts is the EJB container.

The output of your EJB development is a special .jar file, the EJB .jar, which will be deployed to the EJB container of the Application Server on the system where the EJB will be run.

There are a number of Application Server on the market. For this project, I used the Inprise Application Server (IAS) v 4.1.1.

...One thing to keep in mind about IAS when you install it - it hates directory paths with spaces. So make sure not to install at "c:\Program Files\Borland\Application Server". Pre-create the the directory and install to "c:\progra~1\borland\applic~1". If you forget this, you will be confronted with a variety of remarkable error messages and will have a very hard time figuring out what you've done wrong or where to go to fix it.

'Nuff said.

The Enterprise Java Bean

If you know about Java Beans, you already know a lot about EJBs. Like Beans, EJBs offer methods, and getter and setter functions for "properties". They have public interfaces and private and protected internal resources. But they also offer the ability to leverage the computational capability of a separate CPU on a network. And they offer several separate models of how that gets done.

Remember that while the useful interface of the EJB looks like a Bean, it is actually and effectively a separately executing unit under the control of the AppServer. This allows the App Server to implement a variety of kinds of support for the EJB that would not normally be available if it executed in the context of the application.

The four types of EJB are

  • Stateless Session Bean - this is an EJB that doesn't remember anything from one call to the next, though it is generally thought of as belonging to a user "session". This type of EJB can reference external sources of information, and can write out information from the caller, but its internal state is effectively reset on every call. Of course, it's easy to pool these so that there can be more callers than bean instances, which can improve performance.

  • Stateful Session Bean - this is a classic "shopping cart" bean that remembers what the application tells it and what it computes from call to call. Much more than the stateless session bean, the stateful session bean is linked to a particular caller. The stateful session bean can be asked by the container to save or restore its state - that's how the container manages a pool of stateful session beans so that there can be more callers than session beans, despite the presence of state.

  • Self-managed Entity Bean - these beans represent a "database" in the most abstract sense. They can gather and write data to or from any combination of data sources. The container notifies the bean when it should do that. It also supplies the bean with a "primary key" it can use to determine what data to get or save.

  • Container-managed Entity Bean - these beans' properties are mapped to a real database by the container, which takes care of managing reads and writes for the bean. This is nice if the bean maps neatly to a database, but doesn't work as well for more complex "entities".

What kind of bean are we making?

The EJB we construct in this example is a stateful session bean based on our simple Java Bean example. This bean must be stateful, because we want to be able to set divisor and dividend as they change and and then separately retrieve results as needed, so the "do everything in a single method call" model used by stateless beans would not be appropriate. This is also because the stateful model allows an almost direct port of the original Java Bean logic to the EJB environment, emphasizing the commonality between the Java Bean and EJB model.

However, note that this simple EJB could be rewritten to be a stateless session bean, by simply eliminating the properties, and substituting a method call that takes divisor and dividend and returns the result. It can be worthwhile to consider such changes when high performance is required, or when a large number of EJB instances may be needed to support demand.

The Project

An EJB project starts with a regular project (File | New | Project). Then you add an EJB Group (which is where the deployment descriptor will live, describing how the EJB is deployed to the Application Server) with File | New | Empty EJB Group. Then you need to create the bean itself with File | New | Enterprise Java Bean.

The Bean

I'm not going to walk you through the step by step creation of the bean. The JBuilder Enterprise Application Developer's Guide does a fine job of that, and most of the rest of it is just like creating a regular Java Bean. But let's look at the bean itself, and see if there are any differences...

package basicejb;

import java.rmi.*;
import javax.ejb.*;

/**
* Title:       Basic EJB
* Description: Divider as EJB
* Copyright:    Copyright (c) 2000
* Company:
* @author Mark Cashman
* @version 1.0
*/

public class DividerEJB implements SessionBean
{
   private SessionContext sessionContext;
   private String dividend;
   private String divisor;

   public void ejbCreate()
   {
   }
   public void ejbRemove()
   {
   }
   public void ejbActivate()
   {
   }
   public void ejbPassivate()
   {
   }
   
   public void setSessionContext(SessionContext context) {
      sessionContext = context;
   }
   
   public String getDividend()
   {
      return dividend;
   }

   public void setDividend(String newDividend)
   {
      dividend = newDividend;
   }

   public String getDivisor()
   {
      return divisor;
   }

   public void setDivisor(String newDivisor)
   {
      divisor = newDivisor;
   }

   public String getResult()
   {
      String outputValue = "";

      try
      {
         outputValue = (String.valueOf(Double.parseDouble(divisor) / Double.parseDouble(dividend)));
         return outputValue;
      }
      catch (Throwable exception)
      {
         return "";
      };
   }
}

There are only a few minor differences from a straight Java Bean implementation of this... the special functions ejbCreate(), ejbRemove(), ejbActivate(), ejbPassivate(), and setSessionContext(SessionContext context)

In this EJB, none of these (except setSessionContext) even have implementations.

So this part was easy.

Creating the Interfaces

Calling an EJB requires the cooperation of a number of parts that you, as an EJB developer, need to create.

First, there is the "home" interface. The home interface is contacted when the application first wants to make contact with your EJB. The App Server EJB Container supports the home interface and uses it to return a handle to an instance of the EJB, but you (or, actually JBuilder on your behalf) create the home interface and deploy it to the App Server.

package basicejb;

import java.rmi.*;
import javax.ejb.*;

/**
* Title:       Basic EJB
* Description: Divider as EJB
* Copyright:    Copyright (c) 2000
* Company:
* @author Mark Cashman
* @version 1.0
*/

public interface DividerEJBHome extends EJBHome
{
   public DividerEJBRemote create() throws RemoteException, CreateException;
}

Then there is the "remote" interface. The remote interface is what you actually get the handle to. It takes care of managing calls and responses to and from the EJB, and basically duplicates a large part of the interface of the Bean itself. Again, the remote interface is generated by JBuilder and managed by the App Server.

package basicejb;

import java.rmi.*;
import javax.ejb.*;

/**
* Title:       Basic EJB
* Description: Divider as EJB
* Copyright:    Copyright (c) 2000
* Company:
* @author Mark Cashman
* @version 1.0
*/

public interface DividerEJBRemote extends EJBObject
{
   public String getDividend() throws RemoteException;
   public void setDividend(String newDividend) throws RemoteException;
   public String getDivisor() throws RemoteException;
   public void setDivisor(String newDivisor) throws RemoteException;
   public String getResult() throws RemoteException;
}

You'll notice there are no implementations, and functions like ejbActivate are also not included - this is because the App Server actually implements these calls. Also, the remote interface announces that methods can throw remote exceptions, because it is possible that the calls will fail because a portion of the network is down, the App Server is down, or the machine on which it runs is down without failover.

All of these are generated by JBuilder when you create the Bean. If you make changes to your Bean interface (adding / changing / removing properties or methods), you can use Wizards | EJB Interfaces to regenerate the home and remote interfaces from your EJB. In fact, if you have a regular bean you want to turn into an EJB, you can use that wizard to generate the interfaces (though you will have to alter the inheritance of the bean and add the special EJB methods such as ejbCreate(), ejbRemove(), ejbActivate(), ejbPassivate(), and setSessionContext(SessionContext context)).

The Stubs

Imagine... even with an EJB, a home and a remote interface, there are still a few more things you need.

One of these is the "stubs" that will live on the client side and foreward calls to the home and remote interface. If these are not generated, you will get some very difficult to understand messages and failures. This is critical when using IAS as your App Server, and that is the only case I am covering in this article. If you are using Weblogic or some other App Server, consult your documentation.

Making sure the stubs are properly created is easily done. Right click on the home interface .java file and pick Properties. Go to the Build | Visibroker tab and click on the Java2IIOP Settings | Generate IIOP checkbox. That's all you need. The stubs will be properly generated, and after the first compile, they will appear under the home interface.

None of the references discuss whether or not the stubs need to be placed in a separate .jar and deployed to the client system. JBuilder puts them in the EJB .jar. My guess is that the request for your EJB to the home interface causes the serialized stubs to be returned to you, and that your version of the remote interface is actually the deserialized stubs.

Deploying

Of course, compiling your code into .class files is not the end of the job. You need to deploy the EJB into a .jar and give the .jar to the App Server. Obviously, you need to have set up which App Server you are using, and which EJB container within it. You need to set the App Server in Project | Project Properties | Enterprise. If you always use the same App Server, you can set up the default project properties to reflect that. You also need to set up the deployment descriptor by clicking on the project EJB Group and, in the structure pane at the lower left, clicking on the name of the EJB under "EJB Deployment Descriptor". The generated entries are normally fine, but you may need to make changes. Finally, you need to do the deployment - you do this with Tools | EJB Deployment.

The Application

Ok, so you created the EJB, and now you need to test it. You can either use the facilities JBuilder 4 provides for generating a test harness, or you can create your own. Or you can just write the application. That's what I did...

Here's the MainFrame...

package testdividerejb;

import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.naming.*;
import javax.rmi.*;
import javax.ejb.*;
import basicejb.DividerEJBHome;
import basicejb.DividerEJBRemote;
import com.borland.jbcl.layout.*;
import java.beans.*;
import javax.swing.event.*;

public class MainFrame extends JFrame
{
   Box outputBox;
   JLabel outputLabel = new JLabel();
   JLabel outputValue = new JLabel();
   JTextField divisorField = new JTextField();
   Box divisorBox;
   JLabel divisorLabel = new JLabel();
   JTextField dividendField = new JTextField();
   Box dividendBox;
   JLabel dividendLabel = new JLabel();
   JPanel contentPane;
   DividerEJBRemote dividerEJB = null;
   VerticalFlowLayout verticalFlowLayout1 = new VerticalFlowLayout();

   /**Construct the frame*/
   public MainFrame()
   {
      enableEvents(AWTEvent.WINDOW_EVENT_MASK);
      try
      {
         jbInit();
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
   }

   /**Component initialization*/

   private void jbInit() throws Exception
   {
      contentPane = (JPanel) this.getContentPane();
      outputBox = Box.createHorizontalBox();
      divisorBox = Box.createHorizontalBox();
      dividendBox = Box.createHorizontalBox();
      contentPane.setLayout(verticalFlowLayout1);
      this.setResizable(false);
      this.setSize(new Dimension(400, 300));
      this.setTitle("Test Application");
      contentPane.setMinimumSize(new Dimension(120, 69));
      contentPane.setPreferredSize(new Dimension(120, 69));
      contentPane.setToolTipText("");
      outputLabel.setText("Output ");
      divisorLabel.setText("Divisor    ");
      dividendLabel.setText("Dividend ");

      dividendField.addCaretListener(new javax.swing.event.CaretListener()
      {

         public void caretUpdate(CaretEvent e)
         {
            dividendField_caretUpdate(e);
         }
      });

      divisorField.addCaretListener(new javax.swing.event.CaretListener()
      {

         public void caretUpdate(CaretEvent e)
         {
            divisorField_caretUpdate(e);
         }
      });

      contentPane.add(divisorBox, null);
      divisorBox.add(divisorLabel, null);
      divisorBox.add(divisorField, null);
      contentPane.add(dividendBox, null);
      dividendBox.add(dividendLabel, null);
      dividendBox.add(dividendField, null);
      contentPane.add(outputBox, null);
      outputBox.add(outputLabel, null);
      outputBox.add(outputValue, null);

      this.addWindowListener(new java.awt.event.WindowAdapter()
      {
         public void windowClosing(WindowEvent e)
         {
            this_windowClosing(e);
         }
      });

      // Getting access to the EJB...

      Properties properties = System.getProperties();
      Context context = new InitialContext(properties);
      DividerEJBHome dividerEJBHome = (DividerEJBHome) PortableRemoteObject.narrow(context.lookup("DividerEJB"),DividerEJBHome.class); // This produces cast errors if you didn't generate the stubs as indicated above

      dividerEJB = dividerEJBHome.create(); // Returns the remote interface
   }

   /**Overridden so we can exit when window is closed*/

   protected void processWindowEvent(WindowEvent e)
   {
      super.processWindowEvent(e);
      if (e.getID() == WindowEvent.WINDOW_CLOSING)
      {
         System.exit(0);
      }
   }

   void Calculate()
   {
      try // Catch all of the RemoteException thrown by the EJB stubs, here and in the other event handlers
      {
         outputValue.setText(dividerEJB.getResult());
      }
      catch (Throwable exception)
      {
      };
   }

   void dividendField_caretUpdate(CaretEvent e)
   {
      try
      {
         dividerEJB.setDividend(dividendField.getText());
      }
      catch (Throwable exception)
      {
      };

      Calculate();
   }

   void divisorField_caretUpdate(CaretEvent e)
   {
      try
      {
         dividerEJB.setDivisor(divisorField.getText());
      }
      catch (Throwable exception)
      {
      };

      Calculate();
   }

   void this_windowClosing(WindowEvent e)
   {
      try
      {
         dividerEJB.remove();
      }
      catch (Exception exception)
      {
      };
   }
}

Conclusion

This article shows a conclusion in progression from a simple Java application through a Java Bean to this distributed processing EJB-based application, all representing the same capabilities. So you can see that there are a lot of different ways to design and implement your application, and they are not terribly different from each other in terms of style or even complexity of implementation.

One thing that is cumbersome about the EJB model from the application point of view is the need to have logic in the application to contact the home interface and get the remote interface. This means that the application "knows" it is using an EJB.

A nicer model., which requires a little more work, has you provide a Java Bean to wrap all of that. The Java Bean is a sort of proxy that the application calls to use the EJB, without knowing it is using an EJB. A later article will show how to implement that proxy bean.

In the meantime, have fun making EJB applications.

Copyright © 2004 by Mark Cashman (unless otherwise indicated), All Rights Reserved