Exercise 4.1: Your First Beans with JBoss




Exercise 4.1: Your First Beans with JBoss

This exercise allows you to compile and deploy the code from Chapter 4 almost verbatim. You'll see how you can build and deploy an EJB-JAR with an entity bean on the JBoss application server.

Start Up JBoss

Start up JBoss as described earlier in Workbook 1.

Initialize the Database

JBoss comes with an embedded Java database called Hypersonic SQL. All of the examples in the workbook use this database, so there is no need to install and configure any external server. Also, you do not need to create any database tables because the JBoss Java Persistence implementation can be configured to autogenerate them for you when the EJB-JAR is deployed.

Build and Deploy the Example Programs

Perform the following steps:

  1. Open a command prompt or shell terminal and change to the ex04_1 directory created by the extraction process.

  2. Set the JAVA_HOME and JBOSS_HOME environment variables to point to where your JDK and JBoss 4.0 are installed. Examples:


    Windows:

    C:\workbook\ex04_1> set JAVA_HOME=C:\jdk1.5.0
    C:\workbook\ex04_1> set JBOSS_HOME=C:\jboss-4.0.x
    


    Unix:

    $ export JAVA_HOME=/usr/local/jdk1.5.0
    $ export JBOSS_HOME=/usr/local/jboss-4.0
    

  3. Add ant to your execution path. Ant is the build utility.


    Windows:

    C:\workbook\ex04_1> set PATH=..\ant\bin;%PATH%
    


    Unix:

    $ export PATH=33
    ../ant/bin:$PATH
    

  4. Perform the build by typing ant. Ant uses build.xml to figure out what to compile and how to build your JARs.

Before we examine the build file for this example, you might want to take a quick look at the Ant utility at its Jakarta web site home at http://jakarta.apache.org/ant/index.html.

The build.xml file provides Ant with the rules for building the example. Ant compiles the Java source code, builds the EJB-JAR, and deploys the JAR simply by copying it to JBoss' deploy directory. If you are watching the JBoss console window when you run Ant, you will notice that JBoss automatically discovers the EJB-JAR once it has been copied into the deploy directory, and automatically deploys the bean.

Another particularly interesting thing about building EJB-JARs is that there is no special EJB compilation step. Unlike other servers, JBoss does not generate code for client stubs. Instead, it has a lightweight mechanism that creates client proxies when the EJB-JAR is deployed, accelerating the development and deployment cycles.

Deconstructing build.xml

The build.xml file provided for each workbook exercise gives the Ant utility information about how to compile and deploy your Java programs and EJBs. The following build tasks can be executed by typing ant taskname:

  • The default task (typing ant without a task name) compiles the code, builds the EJB-JAR, and deploys the JAR into JBoss. The deployment procedure is a simple copy into the JBoss deploy directory.

  • ant compile compiles all the Java source files.

  • ant clean removes all .class and .jar files from the working directory and undeploys the JAR from JBoss by deleting the file from JBoss' deploy directory.

  • ant clean.db provides you with a clean copy of the Hypersonic SQL database used throughout the exercises. This task works only with Hypersonic SQL.

  • run.client_xxx runs a specific example program. Each exercise in this book will have a run.client rule for each example program.

clean.db can be used only when JBoss is not running.


Here's a breakdown of what is contained in build.xml.

<project name="JBoss" default="ejbjar" basedir="."> 


The default attribute defines the default target that ant will run if you type only ant on the command line. The basedir attribute tells Ant in what directory to run the build.

  <property environment="env"/>
  <property name="src.dir" value="${basedir}/src/main"/>
  <property name="src.resources" value="${basedir}/src/resources"/>
  <property name="jboss.home" value="${env.JBOSS_HOME}"/>
  <property name="build.dir" value="${basedir}/build"/>
  <property name="build.classes.dir" value="${build.dir}/classes"/>

All of these defined properties are variables that Ant will use throughout the build process. You can see that the JBOSS_HOME environment variable is pulled from the system environment and other defined directory paths:

  <path id="classpath">
        <fileset dir="${jboss.home}/client">
            <include name="**/*.jar"/>
        </fileset>
    <pathelement location="${build.classes.dir}"/>
    <pathelement location="${basedir}/client-config"/>
  </path>

To compile and run the example applications in this workbook, add all the JARs in $JBOSS_HOME/client to the Java classpath. Also notice that build.xml inserts the ${basedir}/client-config directory into the classpath. A jndi.properties file in this directory enables the example programs to find and connect to JBoss' JNDI server. The log4j.xml file in this directory configures the logging system (log4j) that JBoss uses.

  <property name="build.classpath" refid="classpath"/>

  <target name="prepare" >
    <mkdir dir="${build.dir}"/>
    <mkdir dir="${build.classes.dir}"/>
  </target>

The prepare target creates the directories where the Java compiler will place compiled classes.

  <target name="compile" depends="prepare">
    <javac srcdir="${src.dir}"
           destdir="${build.classes.dir}"
           debug="on"
           deprecation="on"
           optimize="off"
           includes="**">
            <classpath refid="classpath"/>
    </javac>
  </target>

The compile target compiles all the Java files under the src/main directory. Notice that it depends on the prepare target; prepare will run before the compile target is executed.

<target name="ejbjar" depends="compile">
  <jar jarfile="build/titan.jar">
    <fileset dir="${build.classes.dir}">
          <include name="com/titan/domain/*.class"/>
          <include name="com/titan/travelagent/*.class"/>
    </fileset>
    <fileset dir="${src.resources}/">
        <include name="**/*.xml"/>
    </fileset>
   </jar>
   <copy file="build/titan.jar "
         todir="${jboss.home}/server/default/deploy"/>
</target>

The ejbjar target creates the EJB-JAR file and deploys it to JBoss simply by copying it to JBoss' deploy directory.

<target name="run.client" depends="ejbjar">
  <java classname="com.titan.clients.Client" fork="yes" dir=".">
    <classpath refid="classpath"/>
  </java>
</target>

The run.client target is used to run the example program in this chapter.

<target name="clean.db">
  <delete dir="${jboss.home}/server/default/db/hypersonic"/>
</target>

The clean.db target cleans the default database used by JBoss for the example programs in this book. Remember, you can use it only when JBoss is not running.

<target name="clean">
  <delete dir="${build.dir}"/>
  <delete file="${jboss.home}/server/default/deploy/titan.jar"/>
</target>
</project>

The clean target removes compiled classes and undeploys the EJB-JAR from JBoss by deleting the JAR file in the deploy directory.

JBoss Specifics

No JBoss-specific files are needed in this example. All that is necessary are the JAR'd-up bean classes and interfaces as well as the required persistence.xml file.

Remote JNDI binding

Whenever you deploy a session bean with a remote interface, JBoss creates an entry in the global JNDI namespace. The default JNDI binding for a remote interface is obtained by concatenating the EJB-NAME of the bean with /remote. So, the TravelAgent EJB in this example would be bound to TravelAgentBean/remote. Exercises 4.2 and 4.3 show you how to override the base JNDI binding with an annotation or with a JBoss-specific XML deployment descriptor.

persistence.xml

The persistence.xml file included with this example is basic:

<persistence>
   <persistence-unit>
      <name>titan</name>
      <jta-data-source>java:/DefaultDS</jta-data-source>
      <properties>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      </properties>
   </persistence-unit>
</persistence>

The <name> element is described in Chapter 5 of the EJB book.

The <jta-data-source> element is required in JBoss and references a data source bound in the global JNDI namespace. Here we specify the default Hypersonic SQL database that is created when JBoss boots up. Check out the Appendix to learn how to configure and deploy your own specific data source.

The <properties> element is used to configure JBoss-specific properties. The only property set here is the hibernate.hbm2ddl.auto property, which is used to autogenerate database schemas. The two interesting values for this property are create-drop and update.

The create-drop value creates a table for each entity bean class deployed based on the mapping declared in that bean class and any mapping file. The tables are created when the persistence unit is deployed on the application server. The tables are dropped and removed from the database when the persistence unit is undeployed or redeployed on the application server. This value is very useful for tutorials!

The update value checks to see if the tables already exist in the database. If they exist, they are altered with any updates made to the bean class or mapping files.

Usually the hibernate.hbm2ddl.auto property is not used in a production environment because the database schema is usually created and maintained by a DBA.

Examine and Run the Client Applications

The example program creates a single Cabin bean, populates each of its attributes, and then queries the created bean with its primary key.

Client.java
package com.titan.clients;

import com.titan.travelagent.TravelAgentRemote;
import com.titan.domain.Cabin;

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;

import javax.rmi.PortableRemoteObject;

public class Client
{
    public static void main(String [] args)
    {
        try
        {
            Context jndiContext = getInitialContext( );
            Object ref = jndiContext.lookup("TravelAgentBean/remote");
            TravelAgentRemote dao = (TravelAgentRemote)
                PortableRemoteObject.narrow(ref,TravelAgentRemote.class);

            Cabin cabin_1 = new Cabin( );
            cabin_1.setId(1);
            cabin_1.setName("Master Suite");
            cabin_1.setDeckLevel(1);
            cabin_1.setShipId(1);
            cabin_1.setBedCount(3);

            dao.createCabin(cabin_1);

            Cabin cabin_2 = dao.findCabin(1);
            System.out.println(cabin_2.getName( ));
            System.out.println(cabin_2.getDeckLevel( ));
            System.out.println(cabin_2.getShipId( ));
            System.out.println(cabin_2.getBedCount( ));

        }
        catch (javax.naming.NamingException ne)
        {
            ne.printStackTrace( );
        }
    }

    public static Context getInitialContext( )
        throws javax.naming.NamingException
    {
        return new javax.naming.InitialContext( ); 

    }
}

The getInitialContext( ) method creates an InitialContext with no properties. Because no properties are set, the Java library that implements InitialContext searches the classpath for the file jndi.properties. Each example program in this workbook will have a client-config directory that contains a jndi.properties file. You will be executing all example programs through Ant, and it will set the classpath appropriately to refer to this properties file.

Run the Client application by invoking ant run.client at the command prompt. Remember to set your JBOSS_HOME and PATH environment variables.

The output of the Client application should look something like this:

C:\workbook\ex04_1>ant run.client
Buildfile: build.xml

prepare:

compile:

ejbjar:

run.client:
     [java] Master Suite
     [java] 1
     [java] 1
     [java] 3

The Client application adds a row to the database representing the Cabin bean and does not delete it at the conclusion of the program. You cannot run this program more than once unless you stop JBoss, clean the database by invoking the Ant task clean.db, and restart the application server. Otherwise, you will get the following error:

run.client:
     [java] Exception in thread "main" java.lang.RuntimeException:
org.jboss.tm.JBossRollbackException: Unable to commit
, tx=TransactionImpl:XidImpl[FormatId=257, GlobalId=null:1099/7,
BranchQual=null:1099, localId=0:7], status=STATUS_NO_TR
ANSACTION; - nested throwable: (org.hibernate.exception.GenericJDBCException:
Could not execute JDBC batch update)
     [java]     at org.jboss.aspects.tx.TxPolicy.handleEndTransactionException
(TxPolicy.java:198)
     [java]     at org.jboss.aspects.tx.TxPolicy.endTransaction(TxPolicy.java:180)
     [java]     at org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:87)
     [java]     at org.jboss.aspects.tx.TxInterceptor$Required.invoke
(TxInterceptor.java:195)
     [java]     at org.jboss.aop.joinpoint.MethodInvocation.invokeNext
(MethodInvocation.java:103)
     [java]     at org.jboss.aspects.tx.TxPropagationInterceptor.invoke
(TxPropagationInterceptor.java:76)
...

This doesn't look like much of an error message, but JBoss is relying on the database and JDBC driver to provide a meaningful message.

Viewing the Database

The embedded Hypersonic SQL database has a GUI management console where you view, alter, and create database tables and their contents. To access this console for the default data source that comes with JBoss, first go to http://localhost:8080/jmx-console (see Figure W-8).

Figure W-8. JMX console


Click on the "database=localDB,service=Hypersonic" link shown in Figure W-8. This will bring you to the management page of the database. Scroll down and click on the startDatabaseManager MBean Operation Invoke button shown in Figure W-9.

Figure W-9. Hypersonic management page


Click on the Invoke button to bring up the HSQL Database Manager shown in Figure W-10.

Figure W-10. HSQL Database Manager


When you get to the heavy Java Persistence exercises in Chapters 68, you may find it useful to view the database schema generated by these examples in the Database Manager.