EJB Workshop (under construction)

Contents

  • About this document
  • Software
  • The Application
  • The implementation of the Address Bean
  • Address.java
  • AddressHome.java
  • AddressBean.java
  • ejb-jar.xml
  • AddressClient.java
  • jboss.xml
  • Packaging the EJB
  • build.xml (Ant script)
  • Starting JBoss
  • The Initial Context
  • The Classpath
  • Warnings during the deployment
  • Migration: Integrating MySQL and jboss
  • The Person EJB
  • Person.java
  • PersonHome.java
  • PersonBean.java
  • The modified ejb-jar.xml
  • The modified jboss.xml
  • Jarring and deployment
  • PersonClient.java
  • Integration of JBoss and Tomcat
  • Unit tests with JUnit
  • Resources
  • About this document

    This workshop is the result of my own introductory experiments with EJBs. The material presented in this document is based on my own experiments, web documents, and common introductions to EJBs. It is presented without any guarantee. I shall be grateful to receive notifications about any errors: java@mhoehme.de.

    Software

    All software used in this project is free.

    The JDK

    At the time of the writing of this document, the current version of the JDK is 1.4.1. The examples in this document expect the JDK 1.4, available from http://java.sun.com, and the Enterprise Edition J2SDKEE1.3.1, also available from http://java.sun.com.

    The IDE

    Although it is possible to develop Enterprise Java Beans with a simple text editor, an Integrated Development Environment facilitates many tasks. My personal favourite is NetBeans, version 3.4. Although the Swing GUI of NetBeans has a potential to make its users depressive, it has really good templates. Other people adhere to Eclipse 2.0. It is a good IDE, too. The main feature of Eclipse is that it has its own GUI library, the so-called Standard Widget Toolkit (SWT). The advantage of the SWT is that it mimicks the behaviour of native operating systems, such as Windows. Its disadvantage is that the code has to sacrifice portability. One of the main developers of the SWT is Erich Gamma. Another good IDE is JBuilder. The personal edition is free. Because the Enterprise Edition offers a lot of tools and features that simply the development of EJBs, it lends itself to commercial EJB development.

    To run NetBeans with the JDK 1.4, the startup configuration file C:\NetBeans IDE 3.4\bin\ide.cfg has to be modified. This file contains the startup parameters, and the parameter for jdkhome has to be added:

    -jdkhome C:\Programme\j2sdk1.4.0
    
    If correctly configured (as described above), NetBeans 3.4 displays the API documentation in dynamic popup windows without any furhter configuration.

    The deployment tool: ant

    Ant is a de-facto standard deployment tool. It is available from http://jakarta.apache.org/ant. The current version is 1.5. However, NetBeans comes with ant 1.4.1 integrated, including the optional tasks (ant-optional-1.4.1.jar).

    The EJB Container: jboss

    The current version of jboss is 3.x. This workshop is based on jboss-3.0.2.

    Remark: The "hot deployment" does not seem to work properly in the release candiated of this version, jboss-3.0.0RC_tomcat-4.0.3. However, it works with jboss-3.0.2.

    Instead of jboss I could also have used the reference implementation that comes with J2EE. However, jboss is a commercial-grade EJB container and freely available. It is used in many real-life appliations.

    The database: MySQL

    MySQL is a powerful database server. The current release is version 3.23.49.

    The application

    If you want to utilize the feactures of an EJB container, for example, its thread management, clustering or load balancing, you must observe a number of restrictions. As a bean provider, you supply only a part of the code of an EJB; the other part is supplied by the container. The final code of an EJB is generated by the container-specific tools.

    Both parties, the container provider as well as the EJB provider, are subject to the "EJB contract", as it is laid down in the EJB specification. The current version (public final draft) is 2.1 - and comprises 640 pages! Although the EJB specification is a useful reference, it is not a good starting point for learning EJBs.

    The beginner's difficulties revolve around the application of the restrictions. The more advanced EJB developers and architects are challenged by making the best choice of the options offered by EJB. For example, deciding about entity and session beans, Container-managed persistence (CMP) vs. bean-managed persistence (BMP), using container-managed relationships (CMR), and so on.

    In order to keep things simple, we will start with a simple experimental database. This database will hold addresses and names of persons. The ultimate goal of this experimental application is to be able to generate a list of persons with their addresses. To make things a little bit more interesting, all persons living in the same place shall be listed togehter - for example, for your xmas greeting cards.

    +-----------+ +-----------+ | Person | | Address | +-----------+----------------+-----------+ | ID |+ 0..1| ID | | Title | | Street | | FirstName | | Zip | | LastName | | City | | AddressID | | Country | +-----------+ +-----------+

    We will create two entity beans, Person and Address. These will use container-managed persistence (CMP).

    The Entity beans

    Normally, you will never see an Enterprise Java Bean: it lives inside a container, such as JBoss or Bea Weblogic. The actual code of the bean is generated by the tools, which are part of the container. JBoss, for example, uses the so-called hot deployment: When the JAR file of an EJB is copied to the deployment directory, the generation of the actual EJB code is part of the deployment. Thus you don't need to execute any code-generation tools explicitely; everything is done by the container at the time of the deployment.

    The client interacts with the bean via two interfaces, the home interface, and the business interface (aka remote interface). The home interface usually contains methods to create or delete an EJB. The business interface contains methods such as the getters and setters for the individual attributes, e.g. getName()/setName(). The Bean - as coded by the developer - does not implement these interfaces!

    The client directs its calls to the interfaces, and the container intercepts these calls and delegates them to the actual bean. The client never interacts directly with the bean.

    An EJB consists of a number of files. For the Address EJB there will be the following files:

    For our application, we want to use Container-Managed Persistence (CMP), version 2 (EJB Specification Version 2.x). This means that we leave almost everything to the container, and just supply the names and types of the attributes (such as street etc.), and the abstract persistence scheme declared in the Deployment Descriptor. CMP 2.x takes most of the work off the developer's shoulders. However, it requires even more rules to apply.

    The implementation of the Address Bean

    Address.java

    /*
     * Address.java
     *
     * Created on 28. Dezember 2002, 01:03
     */
    
    package de.mhoehme.addressbase.ejb.address;
    
    import java.rmi.RemoteException;
    import java.util.*;
    
    import javax.ejb.EJBObject;
    
    /**
     * The business interface of the Address EJB.
     *
     * @author Margrit Höhme, 2002
     */
    public interface Address
        extends EJBObject
    {
        public Integer getId () throws RemoteException;
    
        public void setStreet (String street) throws RemoteException;
        
        public String getStreet () throws RemoteException;
        
        public void setZip (String zip) throws RemoteException;
        
        public String getZip () throws RemoteException;
        
        public void setCity (String city) throws RemoteException;
        
        public String getCity () throws RemoteException;
        
        public void setCountry (String country) throws RemoteException;;
        
        public String getCountry () throws RemoteException;;
    }
    

    There must not be a setter method for the primary key (id).

    AddressHome.java

    /*
     * AddressHome.java
     *
     * Created on 28. Dezember 2002, 01:03
     */
    
    package de.mhoehme.addressbase.ejb.address;
    
    import java.rmi.RemoteException;
    
    import javax.ejb.CreateException;
    import javax.ejb.EJBHome;
    import javax.ejb.FinderException;
    
    /**
     * The home interface of the Address EJB
     * 
     * @author Margrit Höhme, 2002
     */
    public interface AddressHome 
        extends EJBHome
    {
        public Address create (Integer id)
            throws CreateException, RemoteException;
        
        public Address create (Integer id, String street,
                               String zip, String city, String country)
            throws CreateException, RemoteException;
        
        public Address findByPrimaryKey (Integer id)
            throws FinderException, RemoteException;
    }
    

    AddressBean.java

    /*
     * AddressBean.java
     *
     * Created on 28. Dezember 2002, 01:03
     */
    
    package de.mhoehme.addressbase.ejb.address;
    
    import javax.ejb.*;
    
    /**
     * The implementation of the Address EJB.
     *
     * @author Margrit Höhme, 2002
     */
    public abstract class AddressBean 
        implements EntityBean
    {
        // Except the two ejbCreate methods, all callback methods are
        // empty methods.
        
        public Integer ejbCreate (Integer id)
            throws CreateException
        {
            System.out.println ("ejbCreate...");
            this.setId (id);
            return null;
        }
        
        public Integer ejbCreate (Integer id, String street,
                                 String zip, String city, String country)
            throws CreateException
        {
            this.setId      (id);
            this.setStreet  (street);
            this.setZip     (zip);
            this.setCity    (city);
            this.setCountry (country);
            return null;
        }
        
        public void ejbPostCreate (Integer id)
        {
        }
        
        public void ejbPostCreate (Integer id, String street,
                                   String zip, String city, String country)
        {
        }
        
        public void setEntityContext (EntityContext ctx)
        {
        }
        
        public void unsetEntityContext ()
        {
        }
        
        public void ejbActivate()
        {
        }
        
        public void ejbPassivate ()
        {
        }
        
        public void ejbLoad()
        {
        }
        
        public void ejbStore () 
        {
        }
        
        public void ejbRemove ()
        {
        }
        
        // The business methods are declared abstract.
    
        public abstract Integer getId();
    
        public abstract void setId (Integer id);
        
        public abstract String getStreet();
    
        public abstract void setStreet (String street);
    
        public abstract String getZip();
    
        public abstract void setZip(String zip);
        
        public abstract String getCity();
    
        public abstract void setCity(String city);
    
        public abstract String getCountry();
    
        public abstract void setCountry(String country);
        
    }
    

    ejb-jar.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE ejb-jar PUBLIC 
        "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
        "http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">
    
    <ejb-jar>
        <enterprise-beans>
            <entity>
                <description>This Address enterprise bean entity 
                    represents an address</description>
                <ejb-name>AddressEJB</ejb-name>
                <home>de.mhoehme.addressbase.ejb.address.AddressHome</home>
                <remote>de.mhoehme.addressbase.ejb.address.Address</remote>
                <ejb-class>de.mhoehme.addressbase.ejb.address.AddressBean</ejb-class>
                <persistence-type>Container</persistence-type>
                <prim-key-class>java.lang.Integer</prim-key-class>
                <reentrant>False</reentrant>
    
                <cmp-version>2.x</cmp-version>
                <abstract-schema-name>Address</abstract-schema-name>
                <cmp-field><field-name>id</field-name></cmp-field>
                <cmp-field><field-name>street</field-name></cmp-field>
                <cmp-field><field-name>zip</field-name></cmp-field>
                <cmp-field><field-name>city</field-name></cmp-field>
                <cmp-field><field-name>country</field-name></cmp-field>
                <primkey-field>id</primkey-field>
            </entity>
        </enterprise-beans>
    
        <assembly-descriptor>
            <security-role>
                <description>
                    This role represents everyone who is allowed full access
                    to the address bean.
                </description>
                <role-name>everyone</role-name>
            </security-role>
    
            <method-permission>
                <role-name>everyone</role-name>
                <method>
                    <ejb-name>AddressEJB</ejb-name>
                    <method-name>*</method-name>
                </method>
            </method-permission>
    
            <container-transaction>
                <method>
                    <ejb-name>AddressEJB</ejb-name>
                    <method-name>*</method-name>
                </method>
                <trans-attribute>Required</trans-attribute>
            </container-transaction>
        </assembly-descriptor>
        
    </ejb-jar>
    

    jboss.xml

    Most containers require some extra configurations. In the case of JBoss, the JAR file must contain the file jboss.xml. In this file, the ejb-name (as defined in the ejb-jar.xml) is mapped to a jndi-name. The jndi-name is used for the lookup.

    <?xml version="1.0" encoding="UTF-8"?>
    <jboss>
      <enterprise-beans>
        <entity>
          <ejb-name>AddressEJB</ejb-name>
          <jndi-name>de/mhoehme/addressbase/ejb/address/Address</jndi-name>
        </entity>
      </enterprise-beans>
    </jboss>
    

    AddressClient.java

    /*
     * AddressClient.java
     *
     * Created on 28. Dezember 2002, 17:50
     */
    
    package de.mhoehme.addressbase.client;
    
    import java.rmi.RemoteException;
    import java.util.Properties;
    
    import javax.ejb.*;                     // CreateException
    import javax.naming.*;
    import javax.rmi.*;
    
    import de.mhoehme.addressbase.ejb.address.*;
    
    /**
     * A simple client for testing the Address EJB.
     *
     * @author Margrit Höhme, 2002
     */
    public class AddressClient 
    {
        public static void main (String[] args)
            throws Exception
        {
            int id = 1;
            
            try
            {
                createAddress (id, "12, Long Lane", "23456", "Big City", "Some Country");
            }
            catch (DuplicateKeyException e)
            {
                System.out.println ("DuplicateKeyException!");
                removeAddress (id);
                createAddress(id, "12, Long Lane", "23456", "Big City", "Some Country");
            }
    
            printAddress(id);
        }
        
        
        private static void createAddress (int id, String street, 
                                   String zip, String city, String country)
            throws CreateException, NamingException, RemoteException
        {
            AddressHome home = getAddressHome();
            
            Address a = home.create (new Integer (id));
            a.setStreet (street);
            a.setZip (zip);
            a.setCity (city);
            a.setCountry (country);
        }
        
        private static void printAddress (int id)
            throws FinderException, NamingException, RemoteException
        {
            AddressHome home = getAddressHome();
            
            Address a = home.findByPrimaryKey (new Integer (id));
            System.out.println ("Street   = " + a.getStreet ());
            System.out.println ("ZIP/City = " + a.getZip() + " " + a.getCity());
            System.out.println ("Country  = " + a.getCountry());        
        }
            
        private static void removeAddress (int id)
            throws FinderException, NamingException, RemoteException, RemoveException
        {
            AddressHome home = getAddressHome();
            
            Address a = home.findByPrimaryKey(new Integer (id));
            a.remove();
        }
        
        private static Context getInitialContext()
            throws javax.naming.NamingException
        {       
            return new InitialContext ();
        }
        
        private static AddressHome getAddressHome()
            throws NamingException
        {
            Context jndiContext = getInitialContext();
            
            Object ref = 
                jndiContext.lookup ("de/mhoehme/addressbase/ejb/address/Address");
            AddressHome home = (AddressHome) 
                PortableRemoteObject.narrow (ref, AddressHome.class);
            return home;
        }
    }
    

    Packaging the EJB

    Before an EJB can be deployed, it must be packed into a JAR file. The file must have the following structure:

    |---de
    |   \---mhoehme
    |       \---addressbase
    |           \---ejb
    |               \---address
    |                   |---Address.class
    |                   |---AddressHome.class
    |                   \---AddressBean.class
    \---META-INF
        |---ejb-jar.xml
        \---jboss.xml
    

    A very common development tool is Ant. The following script might be helpful to get started with Ant. It consists of the following steps:

    1. Initializing the project variables and defining the classpath
    2. compiling the sources
    3. creating a JAR file, and
    4. deploying the JAR file to the deployment directory of JBoss.
    Before invoking Ant, make sure that the environment variable %JAVA_HOME% points to your JDK. Then go to the directory where build.xml is located and simply type ant. To invoke Ant, go to the directory of the build.xml and simply type ant.

    Unless another buildfile has been specified with the -buildefile <file> option, Ant will look for build.xml. If no target has been specified, Ant will executes the default target defined in the <project> tag. For more details please refer to the Ant documentation.

    <?xml version="1.0"?>
    <project name="ejbAddress" basedir="." default="all">
        <target name="init">
            <property name="src"        value="./src"/>
            <property name="classes"    value="./classes" />
            <property name="jboss.home" value="c:\programme\jboss-3.0.2" />
    
            <path id="classpath">
                <pathelement location="${classes}" />
    
                <fileset dir="C:\Programme\j2sdkee1.3.1\">
                    <include name="**/j2ee.jar" />
                </fileset>
            </path>
        </target>
    
        <target name="compile" depends="init">
            <javac srcdir="${src}" destdir="${classes}" debug="true" deprecation="true">
                <classpath refid="classpath" />
            </javac>
        </target>
    
        <target name="address-jar" depends="init, compile">
            <jar jarfile="address.jar" 
                 basedir="${classes}" 
                 includes="**/address/*.class" 
                 excludes="**/*.xml">
                <metainf dir="${src}/Meta-inf" />
            </jar>
        </target>
    
        <target name="all" depends="init,compile,address-jar">
            <echo message="Done."/>
        </target>
        
        <target name="deploy" depends="init, compile, address-jar">
            <copy file="build/address.jar" 
                  todir="${jboss.home}/server/default/deploy"/>
        </target>
    </project>
    

    Further automation includes an automatic deployment, that is, copying the JAR file to the deployment directory.

    Starting JBoss

    JBoss is installed by unzipping the contents of the respective archive into any directory. JBoss requires that the environment variables JAVA_HOME and J2EE_HOME have been set.

    To start jboss, you simply run the run.bat file (Windows) or run.sh script (Unix) in the bin directory. This is how a successful start looks under Windows:

    The most important directory is the server directory. It contains three configurations, all, default and minimal. For our experiments we will use the default configuration in the respective subdirectory.

    To deploy an EJB in jboss, you simply copy its JAR file to the deploy directory of the configuration.

    The default database of JBoss is the Hypersonic database. For your first experiments, we will use it.

    Warnings during the deployment

    If jboss shows a warning during the deployment, check the spelling of the xml version header in the ejb-jar.xml file. Typos and/or the wrong version - for example version 1.1 instead of version 2.0 - are a likely cause. Remember: Cut 'n paste is evil ;-).

    The Initial Context

    The initial context usually requires container-specific properties. Here are two ways to provide the JBoss-specific properties:

    1. Programmatically
      public static Context getInitialContext()
          throws javax.naming.NamingException
      {
          Properties p = new Properties ();
          p.setProperty ("java.naming.factory.initial", 
                         "org.jnp.interfaces.NamingContextFactory");
          p.setProperty ("java.naming.factory.url.pkgs",
                         "org.jboss.naming:org.jnp.interfaces");
          p.setProperty ("java.naming.provider.url", 
                         "localhost");
      
          Context jndiContext = new InitialContext (p);
          // Context jndiContext = new InitialContext();
              // without initialization
              // throws javax.naming.CommunicationException: 
              // Can't find SerialContextProvider
              // if jndi.properties file cannot be found 
              // in the classpath
          return jndiContext;
      }
      
    2. or by placing the following properties in a properties file (e.g. jndi.properties):
      java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
      java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
      java.naming.provider.url=localhost
      

    The Classpath

    To run the client, the following libraries must be in its classpath:

    Migration: Integrating MySQL and jboss

    The default database of jboss is its HypersonicSQL database. The data are saved as a script in the file jboss-3.0.2/server/default/db/hypersonic/default.script. HypersonicSQL is very easy to use. However, we want to use MySQL as a persistence store. The following configuration has been taken from Eclipse IDE for J2EE 1.3 Development (Tomcat 4.x, JBoss 3.x, MySQL 3.x) by Hao Wu (http://www.purposesolutions.com/Resources/EclipseJ2EE.html).

    Preparing MySQL

    MySQL does not support transactions. However, there is a plug-in, INNOBASE. To use INNOBASE, the following steps have to be taken:

    1. The MySQL ini file, located in C:\Winnt\my.ini, has to be modified. This can be done with any text editor, or using the tool winmysqladmin.exe. The file has several paragraphs:
          [mysqld]
          ...
          [WinMySQLadmin]
          ...
          
      Before [WinMySQLadmin], the following lines (at least the underlined ones) have to be inserted:
          innodb_data_home_dir = C:\mysql3.23\ibdata
          innodb_data_file_path = ibdata1:500M;ibdata2:500M
          set-variable = innodb_buffer_pool_size=70M
          set-variable = innodb_additional_mem_pool_size=10M
          innodb_log_group_home_dir = C:\mysql3.23\iblogs
          innodb_log_arch_dir = C:\mysql3.23\iblogs
          innodb_log_archive=0
          set-variable = innodb_log_files_in_group=3
          set-variable = innodb_log_file_size=10M
          set-variable = innodb_log_buffer_size=8M
          innodb_flush_log_at_trx_commit=1
          set-variable = innodb_file_io_threads=4
          set-variable = innodb_lock_wait_timeout=50
          
      The meaning of these lines is described in the mySQL manual, chapter "INNOBASE Tables" (MySQL Manual). Innobase has a manual, too, which is available at http://www.innodb.com/ibman.html.
    2. Create the directories ibdata und iblogs in the mySQL directory manually.
    3. Ensure that mySQL is started with mysqld-max.exe or mysqld-max-nt.exe:
          [WinMySQLadmin]
          Server=C:/Programme/mysql3.23/bin/mysqld-max-nt.exe
          ...
          

    Configuring jboss for MySQL

    1. mm.mysql-2.0.4-src.jar: The mySQL driver mm.mysql-2.0.4-src.jar must be available in the classpath of jboss. You can do this by copying the file to jboss-3.0.2/server/default/lib.
    2. mysql-service.xml: Copy mysql-service.xml from jboss-3.0.2/docs/examples/jca to the deploy directory. jboss-3.0.2/server/default/deploy. The following line has to be adjusted: jdbc:mysql://dell:3306/jbossdb (eg. jdbc:mysql://localhost:3306/addressdb).
    3. login-config.xml: The application policy part ("<application-policy name="name = "MySqlDbRealm">...</application-policy>) contained in the mysql-server.xml file has to be inserted into jboss-3.0.2/server/default/conf/login-config.xml file. Additionally the username and the password for the database have to be adapted.
    4. datasource (standardjaws.xml and standardjbosscmp-jdbc.xml): The data source has to be modified in jboss-3.0.2/server/default/conf/standardjaws.xml and jboss-3.0.2/server/default/conf/standardjbosscmp-jdbc.xml. In jboss-3.0.2/server/default/conf/standardjaws.xml replace
          <datasource>java:/DefaultDS</datasource>
          <datasource-mapping>Hypersonic SQL</datasource-mapping>
      by
          <datasource>java:/MySqlDS</datasource>
          <type-mapping>mySQL</type-mapping>
      and in jboss-3.0.2/server/default/conf/standardjbosscmp-jdbc.xml replace
          <datasource>java:/DefaultDS</datasource>
          <datasource-mapping>Hypersonic SQL</datasource-mapping>
      by
          <datasource>java:/MySqlDS</datasource>
          <datasource-mapping>mySQL</datasource-mapping>    
    5. Creating the database: The database for the EJBs has to be created. It must have the same name as in the mysql-service.xml file (step 2), e.g. addressdb. If you forget to create the database, you will get something like a "NestedSQLException".
    When we are deploying our EJB, the table addressejb will be created. When we are running our simple client, the data will be written into the table.

    After the depeloyment of address.jar:

    The Person EJB

    The Person EJB is created in the same way as the Address EJB. Note: We do not make use of Container-Managed Relationships (CMR) here.

    Person.java

    /*
     * Person.java
     *
     * Created on 29. Dezember 2002, 11:42
     */
    
    package de.mhoehme.addressbase.ejb.person;
    
    import java.rmi.RemoteException;
    import java.util.*;
    
    import javax.ejb.EJBObject;
    
    /**
     * The business or remote interface of the Person EJB.
     *
     * @author Margrit Höhme, 2002
     */
    public interface Person 
        extends EJBObject
    {
        
        public Integer getId () throws RemoteException;
        
        public void setTitle (String title) throws RemoteException;
        
        public String getTitle () throws RemoteException;
        
        public void setFirstName (String firstName) throws RemoteException;
        
        public String getFirstName () throws RemoteException;
        
        public void setLastName (String lastName) throws RemoteException;
        
        public String getLastName () throws RemoteException;
        
        public void setAddress (Integer id) throws RemoteException;
        
        public Integer getAddress () throws RemoteException;
        
    }
    

    PersonHome.java

    /*
     * PersonHome.java
     *
     * Created on 29. Dezember 2002, 11:44
     */
    
    package de.mhoehme.addressbase.ejb.person;
    
    import java.rmi.RemoteException;
    
    import javax.ejb.CreateException;
    import javax.ejb.EJBHome;
    import javax.ejb.FinderException;
    
    /**
     * The home interface for the Person EJB.
     *
     * @author Margrit Höhme, 2002
     */
    public interface PersonHome 
        extends EJBHome
    {
        public Person create (Integer id)
            throws CreateException, RemoteException;
        
        public Person create (Integer id, String title,
                               String firstName, String lastName)
            throws CreateException, RemoteException;
        
        public Person findByPrimaryKey (Integer id)
            throws FinderException, RemoteException;
    }
    

    PersonBean.java

    /*
     * PersonBean.java
     *
     * Created on 29. Dezember 2002, 11:46
     */
    
    package de.mhoehme.addressbase.ejb.person;
    
    import javax.ejb.*;
    
    /**
     * The Bean class of the Person EJB.
     *
     * @author Margrit Höhme, 2002
     */
    public abstract class PersonBean 
        implements EntityBean
    {
        /**
         * This method is not in the remote interface.
         */
        public abstract void setId (Integer id);
        
        public abstract Integer getId ();
        
        public abstract void setTitle (String title);
        
        public abstract String getTitle ();
        
        public abstract void setFirstName (String firstName);
        
        public abstract String getFirstName ();
        
        public abstract void setLastName (String lastName);
        
        public abstract String getLastName ();
        
        public abstract void setAddress (Integer id);
        
        public abstract Integer getAddress ();
        
        // Except the two ejbCreate methods, all callback methods are
        // empty methods.
        
        public Integer ejbCreate (Integer id)
            throws CreateException
        {
            this.setId (id);
            return null;
        }
        
        public Integer ejbCreate (Integer id, String title,
                                 String firstName, String lastName)
            throws CreateException
        {
            this.setId (id);
            this.setTitle (title);
            this.setFirstName (firstName);
            this.setLastName (lastName);
            return null;
        }
        
        public void ejbPostCreate (Integer id)
        {
        }
        
        public void ejbPostCreate (Integer id, String title,
                                   String firstName, String lastName)
        {
        }
        
        public void ejbActivate() throws EJBException, RemoteException {
        }
        
        public void ejbLoad() throws EJBException, RemoteException {
        }
        
        public void ejbPassivate() throws EJBException, RemoteException {
        }
        
        public void ejbRemove() throws RemoveException, EJBException, RemoteException {
        }
        
        public void ejbStore() throws EJBException, RemoteException {
        }
        
        public void setEntityContext(EntityContext entityContext) 
            throws EJBException, RemoteException {
        }
        
        public void unsetEntityContext() throws EJBException, RemoteException {
        }
    }
    

    The modified ejb-jar.xml

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE ejb-jar PUBLIC 
        "-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 2.0//EN"
        "http://java.sun.com/j2ee/dtds/ejb-jar_2_0.dtd">
    
    <ejb-jar>
        <enterprise-beans>
            <entity>
                <description>This Address enterprise bean entity 
                    represents an address</description>
                <ejb-name>AddressEJB</ejb-name>
                <home>de.mhoehme.addressbase.ejb.address.AddressHome</home>
                <remote>de.mhoehme.addressbase.ejb.address.Address</remote>
                <ejb-class>de.mhoehme.addressbase.ejb.address.AddressBean</ejb-class>
                <persistence-type>Container</persistence-type>
                <prim-key-class>java.lang.Integer</prim-key-class>
                <reentrant>False</reentrant>
    
                <cmp-version>2.x</cmp-version>
                <abstract-schema-name>Address</abstract-schema-name>
                <cmp-field><field-name>id</field-name></cmp-field>
                <cmp-field><field-name>street</field-name></cmp-field>
                <cmp-field><field-name>zip</field-name></cmp-field>
                <cmp-field><field-name>city</field-name></cmp-field>
                <cmp-field><field-name>country</field-name></cmp-field>
                <primkey-field>id</primkey-field>
            </entity>
    
            <entity>
                <description>The Person EJB represents a person</description>
                <ejb-name>PersonEJB</ejb-name>
                <home>de.mhoehme.addressbase.ejb.person.PersonHome</home>
                <remote>de.mhoehme.addressbase.ejb.person.Person</remote>
                <ejb-class>de.mhoehme.addressbase.ejb.person.PersonBean</ejb-class>
                <persistence-type>Container</persistence-type>
                <prim-key-class>java.lang.Integer</prim-key-class>
                <reentrant>False</reentrant>
                
                <cmp-version>2.x</cmp-version>
                <abstract-schema-name>Person</abstract-schema-name>
                <cmp-field><field-name>id</field-name></cmp-field>
                <cmp-field><field-name>title</field-name></cmp-field>
                <cmp-field><field-name>firstName</field-name></cmp-field>
                <cmp-field><field-name>lastName</field-name></cmp-field>
                <cmp-field><field-name>address</field-name></cmp-field>
                <primkey-field>id</primkey-field>
            </entity>
        </enterprise-beans>
    
        <assembly-descriptor>
            <security-role>
                <description>
                    This role represents everyone who is allowed full access
                    to the address bean.
                </description>
                <role-name>everyone</role-name>
            </security-role>
    
            <method-permission>
                <role-name>everyone</role-name>
                <method>
                    <ejb-name>AddressEJB</ejb-name>
                    <method-name>*</method-name>
                </method>
            </method-permission>
    
            <container-transaction>
                <method>
                    <ejb-name>AddressEJB</ejb-name>
                    <method-name>*</method-name>
                </method>
                <trans-attribute>Required</trans-attribute>
            </container-transaction>
        </assembly-descriptor>
        
    </ejb-jar>
    

    Modified jboss.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <jboss>
      <enterprise-beans>
    
        <entity>
          <ejb-name>AddressEJB</ejb-name>
          <jndi-name>de/mhoehme/addressbase/ejb/address/Address</jndi-name>
        </entity>
    
        <entity>
          <ejb-name>PersonEJB</ejb-name>
          <jndi-name>de/mhoehme/addressbase/ejb/person/Person</jndi-name>
        </entity>
    
      </enterprise-beans>
    </jboss>
    

    JAR packaging and deployment

    For jarring the complete application, that is, the Address EJB and the Person EJB, and deploying it, we add two ANT tasks:

        <target name="EJB-jar" depends="init, compile">
            <jar jarfile="build/addressbase.jar">
                <fileset dir="${classes}">
                    <include name="de/mhoehme/addressbase/ejb/*/*.class"/>
                </fileset>
                <metainf dir="${src}/META-INF"/>
            </jar>
        </target>
        
        <target name="deploy" depends="init, compile, EJB-jar">
            <copy file="build/addressbase.jar" todir="${jboss.home}/server/default/deploy"/>
        </target>
    

    PersonClient.java

    /*
     * PersonClient.java
     *
     * Created on 29. Dezember 2002, 12:20
     */
    
    package de.mhoehme.addressbase.client;
    
    import java.rmi.RemoteException;
    import java.util.Properties;
    
    import javax.ejb.*;                     // CreateException
    import javax.naming.*;
    import javax.rmi.*;
    
    import de.mhoehme.addressbase.ejb.address.*;
    import de.mhoehme.addressbase.ejb.person.*;
    
    /**
     * A simple client to test the person EJB
     * 
     * @author Margrit Höhme, 2002
     */
    public class PersonClient 
    {
        
        public static void main (String[] args)
            throws Exception
        {
            int id = 1;
            
            try
            {
                createPerson (id, "Mr.", "John", "Doe");
            }
            catch (DuplicateKeyException e)
            {
                System.out.println ("DuplicateKeyException!");
                removePerson (id);
                createPerson (id, "Mr.", "John", "Doe");
            }
    
            printPerson(id);
        }
        
        private static void createPerson (int id, String title, 
                                   String firstName, String lastName)
            throws CreateException, NamingException, RemoteException
        {
            PersonHome home = getPersonHome();
            
            Person p = home.create (new Integer (id));
            p.setTitle (title);
            p.setFirstName (firstName);
            p.setLastName (lastName);
        }
        
        private static void removePerson (int id)
            throws FinderException, NamingException, RemoteException, RemoveException
        {
            PersonHome home = getPersonHome();
            
            Person p = home.findByPrimaryKey(new Integer (id));
            p.remove();
        }
        
        private static void printPerson (int id)
            throws FinderException, NamingException, RemoteException
        {
            PersonHome home = getPersonHome();
            
            Person p = home.findByPrimaryKey (new Integer (id));
            System.out.println ("Person: "+p.getTitle()+" "+
                                           p.getFirstName()+" "+
                                           p.getLastName());
        }
        
        private static Context getInitialContext()
            throws javax.naming.NamingException
        {       
            return new InitialContext ();
        }
        
        private static PersonHome getPersonHome()
            throws NamingException
        {
            Context jndiContext = getInitialContext();
            
            Object ref = 
                jndiContext.lookup ("de/mhoehme/addressbase/ejb/person/Person");
            PersonHome home = (PersonHome) 
                PortableRemoteObject.narrow (ref, PersonHome.class);
            return home;
        }
    }
    

    Integration of jboss and Tomcat

    The default port of the Tomcat servlet engine is 8080, which conflicts with the jboss configuration. When Tomcat and jboss are supposed to run at the same time, they must be integrated.

    To integrate jboss and Tomcat, the following steps are required:

    1. Changing to port 80 (server.xml):
      <Connector className="org.apache.catalina.connector.http.HttpConnector"
          port="80" ... />

    We have created two entity beans, Address and Person. These beans have been deployed in a JBoss/MySQL environment. The next step will be to connect both entity beans, and to create a session bean for accessing both entities in a single step.

    Then we need a User Interface. This could be a simple program like the client programs to test our EJBs, a Swing client, or a web application using servlets, JSPs, and a framework like Struts.

    Unit tests with JUnit

    Finally, let's create some unit tests. XP (eXtreme Programming - http://) advises to write a test before a line of code is written. For an experimental project like the address database it would be overkill. However, to complete our test, we will convert the tasks of AddressClient and PersonClient into veritable unit tests. To this end, we will use the JUnit Framework and tools.

    We start by creating a new sub-directory in our project tree - de.mhoehme.addressbase.test:

    Secondly, we create a junit.TestCase to run all tests, AllTests:

    /*
     * AllTests.java
     *
     * Created on 4. Januar 2003, 23:14
     */
    
    package de.mhoehme.addressbase.test;
    
    import junit.framework.*;
    
    /**
     * The class that runs all tests.
     * 
     * @author Margrit Höhme, 2003
     */
    public class AllTests extends TestCase {
        
        public AllTests(java.lang.String testName) {
            super (testName);
        }
        
        public static void main (java.lang.String[] args) {
            junit.textui.TestRunner.run (suite());
        }
        
        public static Test suite() {
            TestSuite suite = new TestSuite ();
            // here add the individual tests and test suites:
            //suite.addTestSuite (MyTest.class);
            return suite;
        }
    }
    

    When using a GUI version of the test runner, we have to tell JUnit's custom class loader not to ... If we would not do this, we would always receive a ClassCastException when narrowing our home interface. (See JUnit FAQ!). We do this by:

    1. adding our classes to the excluded.properities file in junit.jar
    2. using the modified junit.jar instead of the original downloaded junit.jar
      The junit/runner/excluded.properties file:
      #
      # The list of excluded package paths for the TestCaseClassLoader
      #
      excluded.0=sun.*
      excluded.1=com.sun.*
      excluded.2=org.omg.*
      excluded.3=javax.*
      excluded.4=sunw.*
      excluded.5=java.*
      excluded.6=org.w3c.dom.*
      excluded.7=org.xml.sax.*
      excluded.8=net.jini.*
      excluded.9=de.mhoehme.*
      

    To start our tests, we execute the TestRunner. JUnit comes with several test runners: text-based (junit.textui.TestRunner), with an AWT GUI (junit.awtui.TestRunner), and with a Swing GUI (junit.swingui.TestRunner):

    A successful run of the Swing GUI:

    Resources

    Software Links

    Tutorials and Text Books

    Other documents


    Margrit Höhme, java@mhoehme.de, 2002.
    last modified: