Version 22

    Beginning EJB programming using jBoss

     

    Step 1: creating the Bean

     

    In this step we will write and compile a simple Enterprise JavaBean.

     

    The example -- which is called `Interest' -- is about as simple as an EJB can get: it is a `stateless session bean'. Its job is to calculate the amount of compound interest payable on a sum of money borrowed over a specified term with a specified interest rate. In fact, there is only one functional line of code in the whole package.

     

    You can copy/paste this code and the commands into your system, or you can download the attachment, jBossSimpleExample.zip, at the end of this Wiki page. That zip file contains all of the source code and Windows .bat files for executing the commands. If you installed JBoss in a non-standard location, you'll need to customize environment variable JBOSS_LOC in file set_jboss_loc.bat to represent the location of JBoss on your system.

    -


    EJBs: review

     

    As a reminder, and Enterprise JavaBean has a minimum of three classes.

    • The remote interface. This is the class that exposes the methods of the Bean to the outside world. In the example, the remote interface is the class com.web_tomorrow.interest.Interest

    • The Bean class. This implements the methods specified by the remote interface. In this example, the Bean class is com.web_tomorrow.interest.InterestBean

    • The home interface. This specifies how a new Bean is created, managed and deleted. As a minimum it should specify at least one create() method. There should be an ejbCreate() method in the Bean class for each create() method in the home interface. In this example, the home interface is com.web_tomorrow.InterestHome

     

    Of course, a Bean can include other classes, or even other packages, but the classes listed above are the minimum.

    -


    Coding the classes

     

    We need three classes: the remote interface, the Bean, and the home interface.

     

    The remote interface in this example is very simple.

     

    Interest.java: remote interface for the `interest' EJB

    -


    package com.web_tomorrow.interest;
    import javax.ejb.EJBObject;
    import java.rmi.RemoteException;
    
    /**
      This interface defines the `Remote' interface for the `Interest' 
      EJB. Its single method is the only method exposed to the outside 
      world. The class InterestBean implements the method.
    */
    
    public interface Interest extends EJBObject {
    /**
       Calulates the compound interest on the sum `principle', with 
       interest rate per period `rate' over `periods' time periods. 
       This method also prints a message to standard output; this is 
       picked up by the EJB server and logged. In this way we can 
       demonstrate that the method is actually being executed on the 
       server, rather than the client.
    */
    
      public double calculateCompoundInterest(double principle, 
        double rate, double periods) throws RemoteException;
    }
    

    -


    The remote interface specifies only one "business method" calculateCompoundInterest.

     

    The home interface is even simpler.

     

    Interest.java: remote interface for the `interest' EJB

    -


    package com.web_tomorrow.interest;
    import java.io.Serializable;
    import java.rmi.RemoteException;
    import javax.ejb.CreateException;
    import javax.ejb.EJBHome;
    
    /**
       This interface defines the "home" interface for the "Interest" EJB. 
    */
    
    public interface InterestHome extends EJBHome {
      /**
      Creates an instance of the "InterestBean" class on the server, and 
      returns a remote reference to an Interest interface on the client. 
      */
    
      Interest create()
        throws RemoteException, CreateException;
    }
    

    -


    Finally, here is the Bean class. This is the only one that does any

    real work in this simple example.

     

    Interest.java: remote interface for the `interest' EJB

    -


    package com.web_tomorrow.interest;
    import java.rmi.RemoteException; 
    import javax.ejb.SessionBean;
    import javax.ejb.SessionContext;
    
    /**
       This class contains the implementation for the 
       calculateCompoundInterest method exposed by this Bean. It 
       includes empty method bodies for the methods described by 
       the SessionBean interface; these don't need to do anything 
       in this simple example.
    */
    
    public class InterestBean implements SessionBean {
      /**
       Calulates the compound interest on the sum "principle", with 
       interest rate per period "rate" over "periods" time periods. 
       This method also prints a message to standard output; this 
       is picked up by the EJB server and logged. In this way we
       can demonstrate that the method is actually being executed on 
       the server, rather than the client.
      */
    
      public double calculateCompoundInterest(double principle, 
        double rate, double periods) {
    
        System.out.println ("Someone called 'calculateCompoundInterest!'");
        return principle * Math.pow(1+rate, periods) - principle;
      }
    
      /**
      Empty method body
      */
      public InterestBean() {}
    
      /**
      Empty method body
      */
      public void ejbCreate() {}
    
      /**
      Empty method body
      */
    
      public void ejbRemove() {}
    
      /**
      Empty method body
      */
      public void ejbActivate() {}
    
      /**
      Empty method body
      */
      public void ejbPassivate() {}
    
      /**
      Empty method body
      */
      public void setSessionContext(SessionContext sc) {}
    } 
    

    -


    Notice that most of the methods are empty; they have to exist because

    they're specified by the SessionBean interface, but they don't need to

    do anything in this case.

    -


    Put all three of these .java files in the same directory then type

    this command:

     

    javac -d . -classpath "%JBOSS_LOC%\server\default\lib\jboss-j2ee.jar" .java

     

    The environment variable JBOSS_LOC refers to the location of JBoss

    on your system. By default, this is C:\Program Filess\jboss-4.0.2.

     

    This should create three class files (InterestBean.class,

    Interest.class, and InterestHome.class), with the package tree

    starting in the current working directory (-d .). Since

    InterestClient.java, which we'll talk about later, is also

    in this directory, it will be compiled to InterestClient.class.

     

    With the classes compiled, it's time to create the deployment

    descriptor.

    -


    Step 2: the deployment descriptor

     

    Now it's time to create the deployment descriptor. As a reminder,

    this file tells the EJB server which classes form the Bean, the home

    interface and the remote interface. If there is more than one Bean in

    the package, it indicates also how the Beans interact with one another.

    In this simple example, there is only one Bean so we won't need to

    worry about that part.

     

    Most commercial EJB servers are supplied with graphical tools for

    constructing the deployment descriptor. jBoss does have an XML editor,

    but it's just as easy to construct the deployment descriptor manually.

    Here it is:

    -


    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE ejb-jar PUBLIC 
      "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN" 
      "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd">
    
    <ejb-jar>
    
        <description>JBoss Compound Interest Application</description>
        <display-name>Compound Interest EJB</display-name>
        <enterprise-beans>
           <session>
          <ejb-name>Interest</ejb-name>
          <home>com.web_tomorrow.interest.InterestHome</home>
          <remote>com.web_tomorrow.interest.Interest</remote>
          <ejb-class>com.web_tomorrow.interest.InterestBean</ejb-class>
          <session-type>Stateless</session-type>
          <transaction-type>Bean</transaction-type>
           </session>
        </enterprise-beans>
    </ejb-jar>

    -


    The deployment descriptor must be called ejb-jar.xml and it must

    be in the directory ./META-INF. A common mistake is to name this

    directory "META_INF" (with an underscore, rather than a dash), which

    won't work.

     

    We also need a JBoss-specific deployment descriptor. It's called

    jboss.xml and it must also be in the directory ./META-INF.

    -


    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN"
       "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
    <jboss>
         <enterprise-beans>
              <session>
               <ejb-name>Interest</ejb-name>
               <jndi-name>interest/Interest</jndi-name>
                </session>
            <secure>false</secure>
         </enterprise-beans>
    
    </jboss>

    -


    So now we've got the deployment descriptors ejb-jar.xml, jboss.xml and

    the classes. It's time to package them together.

    -


    Step 3: packaging and deploying the Bean

     

    Creation of the Bean package involves building a JAR archive containing

    the classes and the XML files. For the example Bean we have been

    discussing, this is straightforward; at the top of the directory

    hierarchy run jar like this:

    -


    set JARLIST=com/web_tomorrow/interest/Interest.class 
    set JARLIST=%JARLIST% com/web_tomorrow/interest/InterestHome.class
    set JARLIST=%JARLIST% com/web_tomorrow/interest/InterestBean.class
    set JARLIST=%JARLIST% META-INF
    jar cvf interest.jar %JARLIST%

    -----

    jar builds an archive containing the three classes, and the XML

    file in the META-INF directory. If you list the contents of the

    archive you should see something like this:

    -


          0  06-16-00  11:34   META-INF/
        248  06-16-00  10:12   com/web_tomorrow/interest/Interest.class
        300  06-16-00  10:12   com/web_tomorrow/interest/InterestHome.class
        877  06-16-00  10:12   com/web_tomorrow/interest/InterestBean.class
        549  06-15-00  18:15   META-INF/ejb-jar.xml
        549  06-15-00  18:15   META-INF/jboss.xml

    -----

    Note that the directory structure must be exactly like this, or it

    won't work.

     

    To deploy the Bean on the server, all that's necessary is to copy the

    .jar file to the deploy directory on the server, e.g.,

    C:\Program Files\jboss-4.0.2\server\default\deploy

     

    You can do this as often as you like; the server will detect that the

    file has changed and automatically re-deploy it. During deployment

    you should see the following messages from the server:

    -


    23:08:14,598 INFO  [EjbModule] Deploying Interest
    23:08:14,676 INFO  [EJBDeployer] Deployed: [...]interest.jar

    -----

     

    If everything has gone according to plan, you should now have a Bean

    deployed on the server. We will now create a simple test client that

    runs one of its methods, just to prove that it's working.

    -


    Step 4: coding the test client

    -


    An EJB on its own is no use; we will need at least a simple client

    to use its services. A user of EJBs may be another EJB, and ordinary

    JavaBean, a JSP page, an applet, or a stand-alone application. In

    this example, for simplicity, we will code a simple application.

    This application will create an object of class Interest, and execute

    its one method.

     

    I should point out straight away that the test client I will present

    below does not illustrate how you should code this sort of thing in

    practice; I've done it this way to show exactly what's going on. In

    reality, you will probably want to separate out the RMI-specific

    stuff into a separate class to divide the important functionality

    from the technical details. This class is sometimes called a `stub'.

    Some EJB products will generate stubs automatically. JBoss doesn't

    do this, so you'll have to code the stubs yourself (it isn't

    difficult).

     

    Here is the test client:

     

    Test client: InterestClient.java

    -


    import javax.naming.*;
    import com.web_tomorrow.interest.*;
    import java.util.Hashtable;
    import javax.rmi.PortableRemoteObject; 
    import com.web_tomorrow.interest.*; 
    
    /**
      This simple application tests the `Interest' Enterprise JavaBean 
      which is implemented in the package `com.web_tomorrow.interest'. 
      For this to work, the Bean must be deployed on an EJB server.
    <p>
      <b>IMPORTANT</b> If you want to test this in a real client-server
      configuration, this class goes on the client; the URL of the 
      naming provider specifed in the class must be changed from 
      `localhost:1099' to the URL of the naming service on the server
    */
    
    class InterestClient
    {
      /**
      This method does all the work. It creates an instance of the 
      Interest EJB on the EJB server, and calls its 
      `calculateCompoundInterest()' method, then prints the result 
      of the calculation.
    */
      public static void main(String\[\] args) {
        // 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");
    
        // Enclosing the whole process in a single `try' block is not an ideal way
        // to do exception handling, but I don't want to clutter the program up
        // with catch blocks
        try {
          // Get a naming context
          InitialContext jndiContext = new InitialContext();
          System.out.println("Got context");
    
          // Get a reference to the Interest Bean
          Object ref  = jndiContext.lookup("interest/Interest");
          System.out.println("Got reference");
    
          // Get a reference from this to the Bean's Home interface
          InterestHome home = (InterestHome) 
            PortableRemoteObject.narrow (ref, InterestHome.class);
    
          // Create an Interest object from the Home interface
          Interest interest = home.create();
         
          // call the calculateCompoundInterest() method to do the calculation
          System.out.println 
            ("Interest on 1000 units, at 10% per period, compounded over 2 periods is:");
          System.out.println (interest.calculateCompoundInterest (1000, 0.10, 2));
        } catch(Exception e) {
          System.out.println(e.toString());
        }
      }
    }
    

    -


    It's important to understand that in reality this client will be

    running on a different computer to the Bean server. For testing you

    will probably run them on the same computer, but it will still be

    using a network connection. So the first part of the program

    indicates how to find the server.

    -


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

    -


    There are several other ways to do this, which you may see described

    in other articles. In this case, the server is on the same machine

    as the client (`localhost') and the default naming port is `1099'

    for jBoss (other servers may use different port numbers). If you run

    the client and the server on different machines, you will need to

    change these settings.

     

    The comments in the program should describe how it works; one point

    that requires mention is that the recommended way to get a reference

    to the home interface on the server is like this:

    -


    InterestHome home = (InterestHome) 
         PortableRemoteObject.narrow (ref, InterestHome.class);
    

    -


    which ensures compatibility with different RMI servers (e.g.,

    CORBA). "narrow" ensures that the object returned in "ref" really

    can be converted to an object of class "InterestHome".

     

    The test client doesn't need to be in the same package as the EJB

    classes, and in practice it probably won't be. So it needs to

    import the EJB classes using their fully-qualified class name, like

    this:

    -


    import com.web_tomorrow.interest.*;
    

    -


    You will also need to include jboss-j2ee.jar in the CLASSPATH when

    compiling.

    -


    Step 5: running the test client

     

    We're almost ready to run the test client, but first a note of caution.

     

    In reality the client and the server are likely to be on different

    computers. When you compile the client, the compiler needs to know

    about the organization and methods of classes in the Bean so it can

    do type checking. When you run the client, the run-time engine needs

    to know about the Bean classes, because type-casting is done at

    run-time. So when the client attempts to cast the remote reference

    to a reference to an object of class InterestHome for example, it

    needs to know about this class. This means that you will need the

    Bean interface class files (ie. Interest.class and InterestHome.class but not InterestBean.class) on the client as well as the server even though they

    execute on the server. This is fairly obvious if you think about the

    logic, but it causes all sorts of problems for people who are new

    to RMI programming. If the client can't find the Bean classes, you

    will get error messages like this:

    -


    javax.naming.CommunicationException \[RootExceptionIsjava.lang.ClassNotFoundExceptionCom.web_tomorrow.interest.InterestHome|Root exception is

    java.lang.ClassNotFoundException: com.web_tomorrow.interest.InterestHome\]

    -


    A 'communication exception' is the exception that represents any error

    that can't readily be ascribed to a definite cause. If you get this

    message you need to pay attention to the CLASSPATH on the client.

     

    Even though the EJB classes will run on the server, they need to be

    identified to the client so that the compiler can do the appropriate

    type checking.

    -


    call set_jboss_loc.bat
    set CP=%JBOSS_LOC%\server\default\lib\jboss-j2ee.jar
    set CP=%CP%;%JBOSS_LOC%\server\default\lib\jboss.jar
    set CP=%CP%;%JBOSS_LOC%\server\default\lib\jboss-transaction.jar
    set CP=%CP%;%JBOSS_LOC%\server\default\lib\jnpserver.jar
    set CP=%CP%;%JBOSS_LOC%\lib\jboss-common.jar
    set CP=%CP%;.
    java -classpath "%CP%" InterestClient
    pause
    

    -


    Where the batch file set_jboss_loc.bat sets the environment variable

    like this:

    -


    set JBOSS_LOC=C:\Program Files\jboss-4.0.2
    

    -


    Note the long CLASSPATH; it needs to include the jBoss client classes

    and the EJB classes as well as the standard classpath (if any). This

    needs to go in a shell script; you won't want to type it more than

    once. If all is well, the test client produces the following output:

    -


    Got context
    Got reference
    Interest on 1000 units, at 10% per period, compounded over 2 periods is: 210.00000000000023
    

    -


    A common problem you can see here is a "class not found exception."

    Make sure you include all the jars.

     

    The Bean should produce an output on the server as well; this is to

    show that the Bean has executed on the server, not the client. Look

    for something like this in the server log:

    -


    \[Interest|Interest\] Someone called 'calculateCompoundInterest!'

    -


    Well, that's it. We covered coding, compiling and deploying the Bean,

    and coding and running a simple test client.