How to generate Enterprise Java Beans with EJBDoclet (XDoclet)

Under construction(!)

Author: Ingo Bruell <ibruell@gmx.de>

EJBDoclet is a tool that facilitates coding Enterprise Java Beans. You only have to code one file to generate automatically the needed interfaces and descriptor files. By using ant and the verifier it is very easy to produce correct beans.

EJBDoclet is an OpenSource project started by Rickard Oberg and located at http://sourceforge.net/projects/ejbdoclet.

EJBDoclet has been renamed XDoclet and a new project with this name has been started. XDoclet 1.0 was released in September 2001. Any questions about EJBDoclet and XDoclet must go to the new XDoclet mailing lists hosted at http://sourceforge.net/projects/xdoclet

Requirements

You need to download the ejbdoclet.jar file and put it somewhere in you classpath. I think that the use of Ant is highly recommended (and, for now, it is the only way to use EJBDoclet!).

The tools.jar file from the J2SDK is also needed to call Javadoc, so place it in the classpath, too.

Creating the Bean as a Template

EJBDoclet uses Javadoc tags and the Javadoc mechanism to generate the needed files out of a template. Here there is an example of a section for an Entity Bean class:

/**
 *   This is an account bean. It is an example of how to use the EJBDoclet tags.
 *
 *   @ejb:entity-cmp 1
 *   @ejb:ejb-name bank/Account
 *   @ejb:jndi-name ejb/bank/Account
 *   @ejb:finder Collection findAll() 2
 *   @ejb:finder Collection findByOwner(Customer owner)
 *   @ejb:finder Collection findLargeAccounts(int balance)
 *   @ejb:env-entry foo 1234 java.lang.Integer 3
 *   @ejb:ejb-ref bank/Customer 4
 *   @ejb:security-role-ref admin Administrator
 *   @ejb:permission Teller
 *   @ejb:transaction Required
 *   @ejb:use-soft-locking
 *
 *   JBoss specific 5
 *   @jboss:container-configuration Standard CMP EntityBean
 *
 *   JBoss/JAWS CMP specific 6
 *   @jboss:table-name account
 *   @jboss:create-table true
 *   @jboss:remove-table true
 *   @jboss:tuned-updates true
 *   @jboss:read-only false
 *   @jboss:finder-query findLargeAccounts $1 > 1000
 *   @jboss:finder-order findLargeAccounts balance
 */
public abstract class AccountBean
{
      
1

Here is where it is specified that this is an Entity Bean

2

Definitions of the finder methods

3

Easy definition of environment variables

4

Definition of references to other beans

5

With these tags you can define JBoss-specific parameters (see the EJBDoclet documentation for more details about the tags).

6

And here you can define JAWS-specific parameters (see the EJBDoclet documentation for more details about the tags).

You can see that it is very easy to create the bean template. The JBoss- and JAWS-specific parts should only be used if the JBoss standard values do not fit.

Now you need to define the method level tags like this:

   /**
    * Create account.
    *
    * @ejb:permission Administrator 1
    */
   public AccountPK ejbCreate(AccountData data)
      throws CreateException
   {
   	  setId(data.getId());
      setData(data);

      return null;
   }

   /**
    * Id of this account.
    *
    * This is not remote since the primary key can be extracted by other means.
    *
    * @ejb:pk-field 2
    * @ejb:persistent-field
    *
    * @jboss:column-name pk
    */
   public abstract int getId();

   /**
    * Id of this account.
    *
    */
   public abstract void setId(int id);

   /**
    *  Owner of this account.
    *
    * @ejb:remote-method 3
    * @ejb:persistent-field 4
    * @ejb:permission Administrator
    * @ejb:transaction Supports
    */
   public abstract Customer getOwner();

   /**
    *  Owner of this account.
    *
    */
   public abstract void setOwner(Customer owner);
      
1

Permissions can be defined at the method level with this tag

2

With @ejb:pk-field a primary key field is defined

3

@ejb:remote-method defines a remote method

4

An @ejb:persistent-field defines an attribute that should be stored persistently.

Calling EJBDoclet

For now there is no way to use EJBDoclet without Ant.

Using EJBDoclet with Ant

To use Ant you must download it from the Apache Jakarta project. For EJBDoclet only the ant.jar file is needed.

I prefer a project directory structure like the one JBoss uses:

        project
        +-build
        .  +-[...]
        +-dist
        .  +-[...]
        +-lib
        .  +-ant.jar
        .  +-ejbdoclet.jar
        .  +-[...]
        +-src
        .  +-resources
        .  .  +-test
        .  .  .  +-META-INF
        .  +-main
        .  .  +-test
        .  .  .  +-ejb
        .  .  .  .  +-AccountBean.java
        .  .  .  .  +-CustomerBean.java
      
Here it is my Ant script (build.xml) that first generates the bean interfaces and the descriptor files and then compiles the Java files and packages them into a JAR file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!--
	$Revision: 3.0 $ $Date: 2001/11/18 20:10:58 $ $Author: gropi $
-->
<project name="test" default="main" basedir="../..">

   <target name="init">
      <property name="Name" value="TEST"/>
      <property name="name" value="test"/>
      <property name="version" value="1.0"/>
      <property name="encoding" value="ISO-8859-1"/>
      <property name="typemapping" value="Hypersonic SQL"/>
      <property name="datasource" value="java:/DefaultDS"/>

      <property name="src.dir" value="${basedir}/src/main"/>
      <property name="src.resources" value="${basedir}/src/resources"/>
      <property name="etc.dir" value="${basedir}/src/etc"/>
      <property name="lib.dir" value="${basedir}/lib"/>
      <property name="build.dir" value="${basedir}/build"/>
      <property name="build.lib.dir" value="${basedir}/build/lib"/>
      <property name="build.deploy.dir" value="${basedir}/build/deploy"/>
      <property name="build.classes.dir" value="${basedir}/build/classes"/>
      <property name="build.client.dir" value="${basedir}/build/client"/>
      <property name="dist.dir" value="dist"/>
      <property name="classpath" value="${lib.dir}/jboss-j2ee.jar;${lib.dir}/jta-spec1_0_1.jar" />
      <property name="packages" value="test"/>
      <taskdef name="ejbdoclet" classname="ejbdoclet.EJBDocletTask"
               classpath="${basedir}/lib/ejbdoclet.jar" />
<!--      <property name="build.compiler" value="jikes"/>-->
   </target>

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

  <!-- =================================================================== -->
  <!-- Creates the Bean Classes with EJBDoclet                             -->
  <!-- =================================================================== -->
   <target name="buildbeans" depends="prepare">

      <mkdir dir="${src.resources}/test"/>
      <mkdir dir="${src.resources}/test/META-INF"/>
      <!-- Call EJBDoclet -->
      <ejbdoclet sourcepath="${src.dir}"
                 destdir="${src.dir}"
                 packagenames="test"
                 classpath="${classpath};${basedir}/lib/ejbdoclet.jar"
                 ejbspec="1.1"
                 excludedtags="@version,@author">
        <dataobject/>
        <remoteinterface/>
        <homeinterface/>
        <entitypk/>
        <entitycmp/>
        <deploymentdescriptor xmlencoding="${encoding}"/>
        <jboss xmlencoding="${encoding}"
               typemapping="${typemapping}"
               datasource="${datasource}"/>
      </ejbdoclet>

      <!-- copy the generated descriptor files in the resources directory -->
      <copy file="${src.dir}/ejb-jar.xml" todir="${src.resources}/test/META-INF" />
      <copy file="${src.dir}/jboss.xml" todir="${src.resources}/test/META-INF" />
      <copy file="${src.dir}/jaws.xml" todir="${src.resources}/test/META-INF" />
      <delete>
         <fileset dir="${src.dir}" includes="*.xml"/>
      </delete>
   </target>

  <!-- =================================================================== -->
  <!-- Compiles the source code                                            -->
  <!-- =================================================================== -->
   <target name="compile" depends="prepare">
    <mkdir dir="${build.classes.dir}"/>
    <javac srcdir="${src.dir}"
           destdir="${build.classes.dir}"
           classpath="${classpath}"
           debug="off"
           deprecation="off"
           optimize="on"
           includes="**/*.java"
           excludes="**/*.jbx"
    />
   </target>

  <!-- =================================================================== -->
  <!-- Creates the jar archives                                            -->
  <!-- =================================================================== -->
  <target name="jar" depends="compile">
    <mkdir dir="${build.client.dir}"/>
    <mkdir dir="${build.lib.dir}"/>
    <mkdir dir="${build.deploy.dir}"/>

    <!-- Create Bean jar -->
    <copy todir="${build.classes.dir}">
       <fileset dir="${src.resources}/test" includes="**/*.xml"/>
    </copy>
    <jar jarfile="${build.deploy.dir}/test.jar"
         basedir="${build.classes.dir}"
         includes="test/**/*.class,
                   META-INF/**"
    />
    <delete>
       <fileset dir="${build.classes.dir}/META-INF" />
    </delete>

  </target>

  <!-- =================================================================== -->
  <!-- Verify Beans                                                        -->
  <!-- =================================================================== -->
  <target name="verify" depends="jar">
    <java classname="org.jboss.verifier.Main" fork="true" failonerror="true">
      <classpath path="${classpath}"/>
      <arg value="${build.deploy.dir}/test.jar"/>
    </java>
  </target>

  <!-- =================================================================== -->
  <!-- Creates the binary structure                                        -->
  <!-- =================================================================== -->
   <target name="main" depends="verify">
     <mkdir dir="${dist.dir}"/>
     <mkdir dir="${dist.dir}/bin"/>
     <mkdir dir="${dist.dir}/lib"/>
     <mkdir dir="${dist.dir}/deploy"/>
     <mkdir dir="${dist.dir}/client"/>
     <mkdir dir="${dist.dir}/conf"/>
     <mkdir dir="${dist.dir}/images"/>

     <copy todir="${dist.dir}/client">
        <fileset dir="${build.client.dir}"/>
     </copy>
     <copy todir="${dist.dir}/lib">
        <fileset dir="${build.lib.dir}"/>
     </copy>
     <copy todir="${dist.dir}/deploy">
        <fileset dir="${build.deploy.dir}"/>
     </copy>
     <copy todir="${dist.dir}/lib">
        <fileset dir="${src.resources}/test" includes="*.properties"/>
     </copy>
     <copy todir="${dist.dir}/conf">
        <fileset dir="${etc.dir}/conf"/>
     </copy>

     <copy file="${src.lib}/connector.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/deploy.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jboss-j2ee.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jboss-client.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jbosssx-client.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jbossmq-client.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jndi.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jnp-client.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jta-spec1_0_1.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/stop.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jaas.jar" todir="${dist.dir}/client" />
     <copy file="${src.lib}/auth.conf" todir="${dist.dir}/client" />
     <copy file="${src.lib}/jlfgr-1_0.jar" todir="${dist.dir}/client" />

     <copy file="${etc.dir}/conf/jndi.properties" todir="${dist.dir}/client" />

   </target>

  <!-- =================================================================== -->
  <!-- Cleans up generated stuff                                           -->
  <!-- =================================================================== -->
  <target name="clean" depends="init">
    <delete dir="${build.dir}"/>
    <delete dir="${dist.dir}"/>
  </target>

</project>
      

I have split the generation of the beans and the creation of the application in separate tasks. To generate the beans, call build buildbeans and to create the application, call build.

EJBDoclet throws some Exceptions the first time it is called, but they can be ignored.

The build.xml file generates first the PrimaryKey and DataObject classes, Home and Remote interfaces and the ejb-jar.xml, jboss.xml and jaws.xml descriptors. The Java files are then compiled into the build/classes directory. After that, the files will be archived in test.jar in directory dist/deploy. Now the JAR file can be deployed in JBoss.

If you are either specifying ejbspec="2.0" or nothing (the default is 2.0), it is possible that JBoss will not find the DTDs defined in the deployment descriptors; in that case, just remove those lines or comment them out. For my own use, I have changed the EJBDoclet templates to avoid generating those lines.

Conclusion

In the EJBDoclet archive that can downloaded from http://sourceforge.net/projects/ejbdoclet there is a brief explanation of all possible parameters and a complete example.