Showing posts with label ejb. Show all posts
Showing posts with label ejb. Show all posts

Saturday, 24 December 2011

Running the SavingsAccountEJB Example

Setting Up the Database

The instructions that follow explain how to use the SavingsAccountEJB example with a Cloudscape database. The Cloudscape software is included with the J2EE SDK download bundle.
  1. From the command-line prompt, run the Cloudscape database server by typing cloudscape -start. (When you are ready to shut down the server, type cloudscape -stop.)
  2. Create the savingsaccount database table.
    1. Go to the j2eetutorial/examples directory
    2. Type ant create-savingsaccount-table.
You may also run this example with databases other than Cloudscape. (See the Release Notes of the J2EE SDK for a list of supported databases.) If you are using one of these other databases, you may run the j2eetutorial/examples/sql/savingsaccount.sql script to create the savingsaccount table.

Deploying the Application

  1. In deploytool, open the j2eetutorial/examples/ears/SavingsAccountApp.ear file (FileOpen).
  2. Deploy the SavingsAccountApp application (ToolsDeploy). In the Introduction dialog box, make sure that you select the Return Client JAR checkbox.

Running the Client

  1. In a terminal window, go to the j2eetutorial/examples/ears directory.
  2. Set the APPCPATH environment variable to SavingsAccountAppClient.jar.
  3. Type the following command on a single line:
    runclient -client SavingsAccountApp.ear -name
          SavingsAccountClient -textauth
     
    
  4. At the login prompts, enter guest for the user name and guest123 for the password.
  5. The client should display the following lines:
    balance = 68.25
       balance = 32.55
       456: 44.77
       730: 19.54
       268: 100.07
       836: 32.55
       456: 44.77
       4.00
       7.00

Remote Interface

The remote interface extends javax.ejb.EJBObject and defines the business methods that a remote client may invoke. Here is the SavingsAccount remote interface:
import javax.ejb.EJBObject;
import java.rmi.RemoteException;
import java.math.BigDecimal;

public interface SavingsAccount extends EJBObject {
    
    public void debit(BigDecimal amount)
        throws InsufficientBalanceException, RemoteException;

    public void credit(BigDecimal amount)
        throws RemoteException;
 
    public String getFirstName()
        throws RemoteException;

    public String getLastName()
        throws RemoteException;
   
    public BigDecimal getBalance()
        throws RemoteException;
}
 
The requirements for the method definitions in a remote interface are the same for both session and entity beans:
  • Each method in the remote interface must match a method in the enterprise bean class.
  • The signatures of the methods in the remote interface must be identical to the signatures of the corresponding methods in the enterprise bean class.
  • The arguments and return values must be valid RMI types.
  • The throws clause must include java.rmi.RemoteException.
A local interface has the same requirements, with the following exceptions:
  • The arguments and return values are not required to be valid RMI types.
  • The throws clause does not include java.rmi.RemoteException.

Database Calls

Table 5-1 summarizes the database access calls in the SavingsAccountBean class. The business methods of the SavingsAccountBean class are absent from the preceding table because they do not access the database. Instead, these business methods update the instance variables, which are written to the database when the EJB container calls ejbStore. Another developer might have chosen to access the database in the business methods of the SavingsAccountBean class. This choice is one of those design decisions that depend on the specific needs of your application.
Before accessing a database, you must connect to it.
Table 5-1 SQL Statements in SavingsAccountBean 
MethodSQL Statement
ejbCreateINSERT
ejbFindByPrimaryKeySELECT
ejbFindByLastNameSELECT
ejbFindInRangeSELECT
ejbLoadSELECT
ejbRemoveDELETE
ejbStoreUPDATE

Home Methods

A home method contains the business logic that applies to all entity beans of a particular class. In contrast, the logic in a business method applies to a single entity bean, an instance with a unique identity. During a home method invocation, the instance has neither a unique identity nor a state that represents a business object. Consequently, a home method must not access the bean's persistence state (instance variables). (For container-managed persistence, a home method also must not access relationships.)
Typically, a home method locates a collection of bean instances and invokes business methods as it iterates through the collection. This approach is taken by theejbHomeChargeForLowBalance method of the SavingsAccountBean class. The ejbHomeChargeForLowBalance method applies a service charge to all savings accounts with balances less than a specified amount. The method locates these accounts by invoking the findInRange method. As it iterates through the collection of SavingsAccount instances, theejbHomeChargeForLowBalance method checks the balance and invokes the debit business method. Here is the source code of the ejbHomeChargeForLowBalance method:
public void ejbHomeChargeForLowBalance(
    BigDecimal minimumBalance, BigDecimal charge) 
    throws InsufficientBalanceException {

   try {
       SavingsAccountHome home =
       (SavingsAccountHome)context.getEJBHome();
       Collection c = home.findInRange(new BigDecimal("0.00"),
           minimumBalance.subtract(new BigDecimal("0.01")));

       Iterator i = c.iterator();

       while (i.hasNext()) {
          SavingsAccount account = (SavingsAccount)i.next();
          if (account.getBalance().compareTo(charge) == 1) {
             account.debit(charge);
          }
       }

   } catch (Exception ex) {
       throw new EJBException("ejbHomeChargeForLowBalance: " 
           + ex.getMessage());
   } 
} 
 
The home interface defines a corresponding method named chargeForLowBalance. Since the interface provides the client view, the SavingsAccountClientprogram invokes the home method as follows:
SavingsAccountHome home;
...
home.chargeForLowBalance(new BigDecimal("10.00"), 
   new BigDecimal("1.00"));
 
In the entity bean class, the implementation of a home method must adhere to these rules:
  • A home method name must start with the prefix ejbHome.
  • The access control modifier must be public.
  • The method modifier cannot be static.
The throws clause may include exceptions that are specific to your application; it must not throw the java.rmi.RemoteException.

ejbLoad and ejbStore Methods

If the EJB container needs to synchronize the instance variables of an entity bean with the corresponding values stored in a database, it invokes the ejbLoad and ejbStore methods. TheejbLoad method refreshes the instance variables from the database, and the ejbStore method writes the variables to the database. The client may not call ejbLoad and ejbStore.
If a business method is associated with a transaction, the container invokes ejbLoad before the business method executes. Immediately after the business method executes, the container callsejbStore. Because the container invokes ejbLoad and ejbStore, you do not have to refresh and store the instance variables in your business methods. The SavingsAccountBean class relies on the container to synchronize the instance variables with the database. Therefore, the business methods of SavingsAccountBean should be associated with transactions.
If the ejbLoad and ejbStore methods cannot locate an entity in the underlying database, they should throw the javax.ejb.NoSuchEntityException. This exception is a subclass ofEJBException. Because EJBException is a subclass of RuntimeException, you do not have to include it in the throws clause. When NoSuchEntityException is thrown, the EJB container wraps it in a RemoteException before returning it to the client.
In the SavingsAccountBean class, ejbLoad invokes the loadRow method, which issues a SQL SELECT statement and assigns the retrieved data to the instance variables. The ejbStoremethod calls the storeRow method, which stores the instance variables in the database with a SQL UPDATE statement. Here is the code for the ejbLoad and ejbStore methods:
public void ejbLoad() {

   try {
      loadRow();
   } catch (Exception ex) {
      throw new EJBException("ejbLoad: " + 
         ex.getMessage());
   }
}

public void ejbStore() {

   try {
      storeRow();
   } catch (Exception ex) {
      throw new EJBException("ejbStore: " + 
         ex.getMessage());
   }
}

ejbRemove Method

A client deletes an entity bean by invoking the remove method. This invocation causes the EJB container to call the ejbRemove method, which deletes the entity state from the database. In theSavingsAccountBean class, the ejbRemove method invokes a private method named deleteRow, which issues a SQL DELETE statement. The ejbRemove method is short:
public void ejbRemove() {
    try {
        deleteRow(id);
    catch (Exception ex) {
        throw new EJBException("ejbRemove: " +
        ex.getMessage());
    }
}
 
If the ejbRemove method encounters a system problem, it should throw the javax.ejb.EJBException. If it encounters an application error, it should throw a javax.ejb.RemoveException. 
An entity bean may also be removed directly by a database deletion. For example, if a SQL script deletes a row that contains an entity bean state, then that entity bean is removed.

Monday, 5 December 2011

Turn Ejb To Web Service


File: 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:1099
 
File: Main.java
import javax.naming.InitialContext;
import bean.CountRemote;
public class Main {
  public static void main(String[] a) throws Exception {
    String name = "jexp";
    CountRemote service = null;
    // Context compEnv = (Context) new InitialContext().lookup("java:comp/env");
    // service = (HelloService)new
    // InitialContext().lookup("java:comp/env/ejb/HelloService");
    service = (CountRemote) new InitialContext().lookup("CountBean/remote");
    int countVal = 9;
    service.set(countVal);
    countVal = service.count();
    System.out.println(countVal);
    System.out.println("Calling count() on beans...");
    countVal = service.count();
    System.out.println(countVal);
    service.remove();
  }
}
File: CountBean.java
package bean;
import javax.ejb.Remote;
import javax.ejb.Remove;
import javax.ejb.Stateless;
import javax.jws.WebService;
@Stateless
@Remote(CountRemote.class)
@WebService(serviceName="Counter", portName="CounterPort")
public class CountBean implements CountLocal, CountRemote {
    private int val;
    public int count() {
        System.out.println("count()");
        return ++val;
    }
    public void set(int val) {
        this.val = val;
        System.out.println("set()");
    }
    @Remove
    public void remove() {
        System.out.println("remove()");
    }
}
 
File: CountLocal.java
package bean;
 
import javax.ejb.Local;
@Local
public interface CountLocal  {
    /**
     * Increments the counter by 1
     */
    public int count();
    /**
     * Sets the counter to val
     * @param val
     */
    public void set(int val);
    /**
     * removes the counter
     */
    public void remove();
  }
 
File: CountRemote.java
package bean;
 
import javax.ejb.Remote;
@Remote
public interface CountRemote{
  /**
   * Increments the counter by 1
   */
  public int count();
  /**
   * Sets the counter to val
   * @param val
   */
  public void set(int val);
  /**
   * removes the counter
   */
  public void remove();
}

EJB With Web Method


File: 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:1099
 
File: Main.java
import javax.naming.InitialContext;
import bean.EmployeeServiceRemote;
public class Main {
  public static void main(String[] a) throws Exception {
    EmployeeServiceRemote service = null;
    // Context compEnv = (Context) new InitialContext().lookup("java:comp/env");
    // service = (HelloService)new
    // InitialContext().lookup("java:comp/env/ejb/HelloService");
    service = (EmployeeServiceRemote) new InitialContext().lookup("EmployeeBean/remote");
    service.doAction();
  }
}
 
File: Employee.java
package bean;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PostRemove;
@Entity
public class Employee implements java.io.Serializable {
  private int id;
  private String firstName;
  private String lastName;
  @Id
  @GeneratedValue
  public int getId() {
    return id;
  }
 
  @PostRemove
  public void postRemove()
  {
     System.out.println("@PostRemove");
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getFirstName() {
    return firstName;
  }
  public void setFirstName(String first) {
    this.firstName = first;
  }
  public String getLastName() {
    return lastName;
  }
  public void setLastName(String last) {
    this.lastName = last;
  }
}
 
File: EmployeeBean.java
package bean;
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebService;
@Stateless(name = "EmployeeBeanEJB")
@WebService(serviceName = "EmployeeBeanWebService", 
            targetNamespace = "http://www.jexp.ru/ejb3/credit")
public class EmployeeBean implements EmployeeServiceLocal, EmployeeServiceRemote {
  public EmployeeBean() {
  }
  @WebMethod(operationName = "CreditCheck")
  public boolean validateCC(String cc) {
    return true;
  }
  public void doAction() {
    System.out.println("Processing...");
  }
}
 
File: EmployeeServiceLocal.java
package bean;
import javax.ejb.Local;
import javax.ejb.Remote;
 
@Local
public interface EmployeeServiceLocal{
  public void doAction();
}
 
File: EmployeeServiceRemote.java
package bean;
import javax.ejb.Stateless;
import javax.jws.WebService;
 
public interface EmployeeServiceRemote {
  public void doAction();
 
}

EJB Based Web Services


File: 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:1099
 
File: Main.java
import java.util.Date;
import javax.naming.InitialContext;
import bean.EmployeeServiceRemote;
 
public class Main {
  public static void main(String[] a) throws Exception {
    EmployeeServiceRemote service = null;
    // Context compEnv = (Context) new InitialContext().lookup("java:comp/env");
    // service = (HelloService)new InitialContext().lookup("java:comp/env/ejb/HelloService");
    service = (EmployeeServiceRemote) new InitialContext().lookup("EmployeeBean/remote");
 
 
 
 
    //service.doAction();
  }
}
 
File: Employee.java
package bean;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.PostRemove;
 
@Entity
public class Employee implements java.io.Serializable {
  private int id;
  private String firstName;
  private String lastName;
  @Id
  @GeneratedValue
  public int getId() {
    return id;
  }
 
  @PostRemove
  public void postRemove()
  {
     System.out.println("@PostRemove");
  }
  public void setId(int id) {
    this.id = id;
  }
  public String getFirstName() {
    return firstName;
  }
  public void setFirstName(String first) {
    this.firstName = first;
  }
  public String getLastName() {
    return lastName;
  }
  public void setLastName(String last) {
    this.lastName = last;
  }
}
 
File: EmployeeBean.java
package bean;
import javax.ejb.Stateless;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
 
@WebService(name = "MyEmployee", serviceName = "MyEmployeeService")
//@Stateless
public class EmployeeBean implements EmployeeServiceLocal, EmployeeServiceRemote {
  @PersistenceContext(unitName = "EmployeeService")
  private EntityManager entityManager;
  public EmployeeBean() {
  }
  @WebMethod
  public void createEmployee(@WebParam(name = "employee")Employee c) {
    entityManager.persist(c);
  }
  @WebMethod
  @WebResult(name = "Employee")
  public Employee findEmployee(@WebParam(name = "ID")int pKey) {
    return entityManager.find(Employee.class, pKey);
  }
}
 
File: EmployeeServiceLocal.java
package bean;
 
import javax.ejb.Local;
 
@Local
public interface EmployeeServiceLocal {
  public void createEmployee(Employee c);
  public Employee findEmployee(int id);
}
 
File: EmployeeServiceRemote.java
package bean;
import javax.ejb.Remote;
 
@Remote
public interface EmployeeServiceRemote {
  public void createEmployee(Employee c);
  public Employee findEmployee(int id);

How to generate Enterprise Java Beans with EJBDoclet (XDoclet)


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 theverifier 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: 1.2 $ $Date: 2002/12/19 14:48:24 $ $Author: cvsuser $
-->
<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.xmljboss.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.