JaVa
   

Building EJBs with Ant and XDoclet

In we spent quite a bit of time developing Enterprise JavaBeans to support both an entity bean and a session bean. As you already know, building EJBs require quite a bit of work between several files. In this chapter, we continue with our use of XDoclet and see how to use it to build EJBs from the ground up. The topics we cover include database mappings, relationships, and primary keys.

Generating XDoclet EJBs

Now we will set up an example to illustrate how easily you can build EJBs when using XDoclet.Consider a sitaution where we have two container-managed persistence (CMP) entity beans called Student and Unit. There is a one-to-many relationship (a Unit can have many Students), and a session bean can be used to access the information within and between the Student and Unit beans.

Database Setup

Unlike in , we actually map the entity beans to a database so "real" information can be obtained. The database tables we will model are

CREATE TABLE unit (
 ID int not null auto_increment primary key,
 name varchar(64)
);
CREATE TABLE student (
 ID INT not null auto_increment primary key,
 firstname VARCHAR (80),
 lastname VARCHAR (80),
 unitid INT, );


Each of the tables should be populated with appropriate data like the following:

INSERT INTO unit (NAME) VALUES ('CSC100');
INSERT INTO unit (NAME) VALUES ('ACC325');
INSERT INTO unit (NAME) VALUES ('EE785');
INSERT INTO unit (NAME) VALUES ('CE850');
INSERT INTO unit (NAME) VALUES ('MAT125');
INSERT INTO student VALUES('Jim', "Smith", 1);
INSERT INTO student VALUES('Jane', "Wellington", 3);
INSERT INTO student VALUES('Tim', "Docler", 2);
INSERT INTO student VALUES('Jim', "Smith", 4);
INSERT INTO student VALUES('John', "Doe", 2);


Creating Entity Beans

In our previous look at Enterprise JavaBeans, we were required to write code for three different files: the bean itself, a remote interface, and a home interface. If we made any change to the bean class, then both the remote and local interfaces had to change as well. This is of course a situation in which a typo results in a compile error and more than a bit of frustration. Since the bean class is the controlling feature of an EJB, it should be able to generate the other files, and this is what XDoclet does for us. In the following listing, consider the entity bean for the Unit database table.

package ejb;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import java.util.Collection;
/**
 * This entity bean maps to a Unit table and uses CMP version 2.x
 *
 * @ejb.bean
 * type="CMP"
 * cmp-version="2.x"
 * 
 * schema="Unit"
 * local-jndi- * view-type="local"
 * primkey-field="id"
 *
 * * @ejb.pk class="java.lang.Integer"
 *
 * @ejb.home generate="local" local-class="ejb.UnitHome"
 * @ejb.interface generate="local" local-class="ejb.Unit"
 *
 * * @ejb.finder
 * signature="Dept findByUnitName(java.lang.String name)"
 * unchecked="true"
 * query="SELECT OBJECT(unit) FROM Unit unit where unit.name = ?1"
 * result-type-mapping="Local"
 *
 * @ejb.finder
 * signature="Collection findAll()"
 * unchecked="true"
 * query="SELECT OBJECT(unit) FROM unit unit"
 * result-type-mapping="Local"
 *
 * @ejb.persistence table- 
 *
 */
public abstract class UnitBean implements EntityBean {
 /**
 *
 * @ejb.create-method
 */
 public Integer ejbCreate(String name) throws CreateException {
 setName(name);
 return null;
 }
 public void ejbPostCreate(String name) throws CreateException{ }
 /**
 * This is a cmp field. The cmp field is read only.
 * And it is the primary key.
 *
 * @ejb.interface-method view-type="local"
 * @ejb.persistence column- 
 */ public abstract Integer getId();
 public abstract void setId(Integer id);
 /**
 * This is a cmp field. The cmp field is read/write.
 * @ejb.interface-method view-type="local"
 * @ejb.persistence column- 
 */ public abstract String getName();
 public abstract void setName(String name);
 /**
 * @return return employees in this department
 *
 * @ejb.interface-method view-type="local"
 *
 * @ejb.transaction type="Required"
 *
 * @ejb.relation
 * 
 * role- 
 * target-role- 
 * target-cascade-delete="no"
 */ public abstract Collection getStudents();
 /** @ejb.interface-method view-type="local" */
 public abstract void setStudents(Collection collection); public void setEntityContext(EntityContext context){ }
 public void unsetEntityContext(){ }
 public void ejbRemove()throws javax.ejb.RemoveException{ }
 public void ejbLoad(){ }
 public void ejbStore(){ }
 public void ejbPassivate(){ }
 public void ejbActivate(){ }
}


All of the work for our EJBs is accomplished within the Bean class using a variety of XDoclet tags. With an appropriate Ant task, the deployment descriptor and other support files are created automatically.

Bean Class Tags

The XDoclet tags for generating EJBs appear in either the class or method level. In this section, we discuss those tags that appear in the comment above the actual Bean class definition.

@ejb.bean

The first and probably the most important tag is called ejb.bean. This tag allows the developer to specify the type of bean being created. In our example. there are seven attributes to the tag:

When XDoclet executes the ejb.bean tag as we've defined it, the following information is contributed to the ejb-jar.xml file:

<entity> <ejb-name>UnitBean</ejb-name>
<ejb-class>ejb.UnitBean</ejb-name>
<reentrant>False</reentrant> <cmp-version>2.x</cmp-version> <abstract-schema-name>Unit</abstract-schema-name> <primkey-field>id</primkey-field>
</entity>


@ejb.pk

If you have a primary key for your table and resulting modeling EJB, you will need to specify the Java class type that directly relates to the type in the database. Since the most common type is an integer, the conversion from a database integer to a Java integer isn't too big a deal, but if you are using a compound key (not discussed in this chapter), you will need to give more thought to the class issue. We have a single attribute called class="java.lang.Integer" for the tag. When parsed, the following element is added to the ejb-jar.xml file:

<prim-key-class>java.lang.Integer</prim-key-class>


@ejb.home and @ejb.interface

As you might expect, XDoclet automatically creates the home and interface files for our entity bean. The names of the files and resulting code are specified using the @ejb.home and @ejb.interface tags. Here's the tags we used:

@ejb.home generate="local" local-class="ejb.UnitHome" @ejb.interface generate="local" local-class="ejb.Unit"


We use the generate attribute to specify the type of home and/or interface. The values are local, remote, or both. XDoclet creates the appropriate classes using the values located in the local-class attributes. In addition to the classes, the following entries are added to the ejb-jar.xml file:

<entity>
<local-home>ejb.UnitHome</local-home>
 <local>ejb.Unit</local> </entity>


Of course, if you specify a different generate value XDoclet adds the appropriate classes and entries to the XML file.

@ejb.finder

The finder methods are an important part of any entity bean, and XDoclet provides support for defining those methods within the bean. The signature of the finder is defined as well as the query to be used. Notice the use of the result-type-mapping attribute and its value of local. XDoclet generates the finder methods in the home as well as appropriate elements in the deployment descriptor. The methods defined for our example are as follows:

public interface UnitHome extends javax.ejb.EJBLocalHomc {
public ejb.Unit findByUnitName(java.lang.String name) throws javax.ejb.FinderException;
public java.util.Collection findAll() throws javax.ejb.FinderException;
public ejb.Unit findByPrimaryKey(java.lang.Integer pk) throws javax.ejb.FinderException;


The resulting elements for the ejb-jar.xml file are as follows:

<query>
<query-method>
<method-name>findByUnitName</method-name>
<method-params>
<method-param>java.lang.String</method-param>
</method-params>
</query-method>
<result-type-mapping>Local</result-type-mapping>
<ejb-ql>SELECT OBJECT(unit) FROM Unit unit where unit.name =?1
</ejb-ql>
</query>
<query>
<query-method>
<method-name>findAll</method-name>
<method-params></method-params>
</query-method>
<result-type-mapping>Local</result-type-mapping>
<ejb-ql>SELECT OBJECT(unit) FROM Unit unit</ejb-ql>
</query>


@ejb.persistence

Unfortunately, the EJB specification doesn't have a common Object/Relational mapping defined. For this reason, many app servers came up with their own spec and subsequently their own ejb tags. Resin is a good case in point. There are a few specific Resin ejb tags to handle the relationship between the EJB and the entity bean. However, most containers now map entities to classes and fields to columns. Recently the XDoclet specification has added a tag called @ejb.persistence that we can use to specify the table our entity bean relates to. In our example, the UnitBean has an entry called @ejb.persistence table- . The table-name attribute is the actual table name for our environment. The resulting element in the ejb-jar.xml file is as follows:

<entity>
<sql-table>unit</sql-table>
</entity>


When working with a specific app server, be sure to consult the product's documentation to determine if any specific tags are needed to handle the entity bean-table relationship.

Create Method Tags

An entity bean is required to have a create method. The code in our UnitBean is this:

/*
* @ejb.create-method
*/
public Integer ejbCrate(String name) throws CreateException setName(name);
return null;


The create tag doesn't require any attributes. This tag will use cause XDoclet to generate a corresponding create method in the home. For example:

public interface UnitHome extends javax.ejb.EJBLocalHome {
public ejb.Unit create(java.lang.String name) ()
throws javax.ejb.CreateException;
}


Getter/Setter Method Tags

For every field in the database table we are modeling, there will be corresponding getter/setter methods in the bean implementation. For the Unit table we have two different fields—the ID and the Name—so we need two pairs of setter/getter methods. Each of the pairs will have three XDoclet tags—persistence-field, method-view, and persistence—that could appear.

@ejb.persistence-field

The @ejb.persistence-field tag is used to designate the getter method as part of the CMP declaration and will subsequently signal XDoclet to produce an appropriate entry in the deployment descriptor. In our code, we have used this tag to specify that both the ID and Name fields are persistent. Notice that you don't put the name of the field with the tag. XDoclet determines the appropriate field based on the name of the getter method.

@ejb.persistence

If you have a situation where the getter/setter method signature doesn't relate to the name of the database field they represent or the vendor requires it, the @ejb.persistence tag can be used along with the column-name attribute. For example, we could have the following:

/**
* @) ejb. persistence column- * */
public abstract Integer getId();


@ejb.interface-method

The @ejb.interface-method is used to specify the type of method for the appropriate getter/setter pairs and signal that the method should appear in the interface. The attribute for the tag is view-type. The value will be either local, remote, or both, and XDoclet creates the appropriate elements. For example, in the getID() method, we would see the following code in the interface:

public interface Unit extends javax.ejb.EJBLocalObject {
public java.lang.Integer getId();
}


Relations

Probably one of the most difficult aspects of building EJBs is signaling to the beans the various relationships that exist within the relational database. In our example, there is a one-to-many relationship between the Unit and Student tables; there can be many Students to a Unit. Via the @ejb.relation tag, XDoclet allows you to define a CMR relationship.

@ejb.relation

For our code, the tags are defined as follows:

 /**
 * @return return employees in this department
 *
 * @ejb.interface-method view-type="local"
 *
 * @ejb.transaction type="Required"
 *
 * @ejb.relation
 * 
 * role- 
 * target-role- 
 * target-cascade-delete="no"
 */ 


We have to give the relationship a name and then describe the role. Notice the use of the target-cascade-delete attribute. This attribute is used to determine whether, if a unit is deleted, all of the corresponding students should be deleted as well. In our case, we just use a value of no. As you will see in the StudentBean, we include another @ejb.relation tag because we need to specify each side of the relationship. The result of both @ejb.relation tags is shown here:

<relationships >
<ejb-relation >
<ejb-relation-name>StudentsInAunitRelation</ejb-relation-name>
<ejb-relationship-role >
<ejb-relationship-role-name>StudentInAUnit</ejb-relationship-role-name>
<multiplicity>Many</multiplicity>
<relationship-role-source >
<ejb-name>StudnetBean</ejb-name>
</relationship-role-source>
<cmr-field >
<cmr-field-name>unit</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role >
<ejb-relationship-role-name>UnitHasStudents</ejb-relationship-role-name>
<multiplicity>One</multiplicity>
<relationship-role-source >
<ejb-name>UnitBean</ejb-name>
</relationship-role-source>
<cmr-field >
<cmr-field-name>students</cmr-field-name>
<cmr-field-type>java.util.Collection</cmr-field-type>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
</relationships>


@ejb.transaction

If your relationship requires a transaction to work properly, use the @ejb.transaction tag and its type attribute. In our case, we need to make sure that the unit isn't changed when a student is added to the table; therefore, we set the type attribute to required. The underlying database must support transactions in order for the process to work correctly.

Student Bean Code

We have another table to be modeled called Student, which contains all of the student information. Students are enrolled in a unit, and the relationship is kept between the two tables. The next listing shows the Student entity bean code along with its XDoclet tags. All of the tables are the same as described earlier.

package ejb;
import javax.ejb.EJBException;
import javax.ejb.EntityBean;
import javax.ejb.EntityContext;
import javax.ejb.CreateException;
import javax.naming.*;
/**
 * This entity bean represents Students in a table.
*
 * @ejb.bean
 * type="CMP"
 * cmp-version="2.x"
 * 
 * schema="Student"
 * local-jndi- * view-type="local"
 * primkey-field="id"
 *
 * * @ejb.pk class="java.lang.Integer"
 *
 * @ejb.home generate="local" local-class="ejb.StudentHome"
 * @ejb.interface generate="local" local-class="ejb.Student"
 *
 * @resin-ejb.entity-bean
 * sql-table="STUDENT"
 *
 * * @ejb.finder
 * signature="Employee findByLastName(java.lang.String name)"
 * unchecked="true"
 * query="SELECT OBJECT(student) FROM Student student where student.lastName = ?1"
 * result-type-mapping="Local"
 *
 * @ejb.finder
 * signature="Collection findAll()"
 * unchecked="true"
 * query="SELECT OBJECT(student) FROM Student student"
 * result-type-mapping="Local"
 *
 *
 *
 */
public abstract class StudentBean implements EntityBean {
 /**
 *
 * @ejb.create-method
 */
 public Integer ejbCreate(
 String firstName,
 String lastName,
 Integer unitid)
 throws CreateException {
 setFirstName(firstName);
 setLastName(lastName);
 try {
 // The JNDI context containing local EJBs
 Context cmp = (Context) new InitialContext().lookup("java:comp/env/ejb");;
 // Get the house stub
 DeptHome home = (DeptHome) cmp.lookup("UnitBean");
 setUnit(home.findByPrimaryKey(unitid));
 } catch (Exception e) {
 throw new EJBException(e);
 }
 return null;
 }
 public void ejbPostCreate(
 String firstName,
 String lastName,
 Integer deptid)
 throws CreateException {
 }
 /**
 * This is a cmp field. The cmp field is read only.
 * And it is the primary key.
 *
 * @ejb.pk-field
 * @ejb.persistent-field
 * @ejb.interface-method view-type="local"
 * @ejb.persistence column- 
 */
 public abstract Integer getId();
 public abstract void setId(Integer id);
 /**
 * This is a cmp field. The cmp field is read/write.
 * @ejb.interface-method view-type="local"
 * @ejb.persistence column- 
 */
 public abstract String getFirstName();
 public abstract void setFirstName(String name);
 /**
 * This is a cmp field. The cmp field is read/write.
 * @ejb.interface-method view-type="local"
 * @ejb.persistence column- 
 */
 public abstract String getLastName();
 public abstract void setLastName(String name);
 /**
 * @return Return the group this user is in.
 *
 * @ejb.interface-method view-type="local"
 *
 * @ejb.transaction type="Required"
 *
 * @ejb.relation
 * 
 * role- 
 * target-role- 
 *
 * @resin-ejb.relation sql-column="UNITID"
 *
 */
 public abstract Unit getUnit();
 /** @ejb.interface-method view-type="local" */
 public abstract void setUnit(Unit unit);
 /** @ejb.interface-method view-type="local" */
 public Integer getUnitId() {
 return getUnit().getId();
 }
 public void setEntityContext(EntityContext context) {
 }
 public void unsetEntityContext() {
 }
 public void ejbRemove() throws javax.ejb.RemoveException {
 }
 public void ejbLoad() {
 }
 public void ejbStore() {
 }
 public void ejbPassivate() {
 }
 public void ejbActivate() {
 }
}


Creating a Session Bean

To use the two entity beans we have created, let's rely on our XDoclet experience to create a session bean as well. The next listing shows the code for the bean. For the most part, the tags are the same as those in the entity bean, but there are a few different ones. Within the @ejb.bean tag, a type attribute is available to let the system know whether the session bean is stateless. In addition, there is a tag called @ejb.ejb-ref that adds a reference to each of the entity beans in our example. The ejb-name attribute is set to the class name of the bean, and the view-type attribute is set to local, remote, or both.

package ejb;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import java.util.*; import util.*;
/**
 * Provides a session facade that works with cmp/cmr * * This bean uses container-managed transactions. * This bean does not maintain any state; thus, it can be stateless.
 *
 *
 * @ejb.bean type="Stateless"
 * local-jndi- * * * @ejb.ejb-ref ejb- view-type="local"
 * @ejb.ejb-ref ejb- view-type="local"
 * */
public class StudentTest implements SessionBean {
 /**
 * Get a list of all the units.
 *
 * @ejb.interface-method view-type="local"
 * @ejb.transaction type="Required"
 */
 public String[] getUnits() {
 ArrayList unitList = new ArrayList();
 Collection collection = LocalFinderUtils.findAll("unitBean");
 Iterator iterator = collection.iterator();
 while (iterator.hasNext()) {
 unitList.add((Unit)iterator.next()).getName());
 }
 return (String[]) UnitList.toArray(new String[unitList.size()]);
 }
 /**
 * The ejbActivate method. * Since this is stateless, it should never get called.
 */
 public void ejbActivate() {
 }
 /**
 * The ejbActivate method. * Since this is stateless, it should never get called.
 */
 public void ejbPassivate() {
 }
 /**
 * The ejbRemove method. * */
 public void ejbRemove() {
 }
 /**
 * The ejbRemove method. * */
 public void ejbCreate() {
 }
 /**
 * Set session context – but it's stateless so don't implement
 */
 public void setSessionContext(SessionContext sc) {
 }
}


Ant Target for XDoclet

In order for the XDoclet tags to generate the necessary code for our EJBs, we need an appropriate Ant build script. Our example script is shown in the next listing. The most important part of the build script is the element <target >. Within this element, we define the EjbDoclet task as well as the subtasks that we want Ant to execute. As you can see, we've requested seven tasks: <localinterface/>, <localhomeinterface/>, <remoteinterface/>, <homeinterface/>, <entitypk/>, and <deploymentdescriptor>. Each of these subtasks will process the appropriate tags from the two entity and one session bean source code files. The result will be all of the bean code, support files, and deployment descriptor created automatically and configured for our example.

<?xml version="1.0"?>
<project default="deploy">
 <property file="build.properties"/>
 <path >
 <fileset dir="${lib}"/>
 <fileset dir="${WEBINF}/lib" />
 </path>
 <path >
 <path ref />
 <fileset dir="${xdocletlib}">
 <include name="*.jar"/>
 </fileset>
 </path>
 <target >
 <mkdir dir="${output}/war" />
 <mkdir dir="${dest}" />
 </target>
 <target >
 <delete dir="${output}/war" />
 <delete>
 <fileset dir="${dest}">
 <exclude name="**/*.java" />
 <exclude name="**/*.properties" /> </fileset>
 </delete>
 <delete dir="${webapps}/${app}" />
 <delete dir="${webapps}/${app}.war" />
 <delete>
 <fileset dir="${gen.src}">
 <include name="**/*.java"/>
 </fileset>
 </delete>
 <delete>
 <fileset dir="META-INF">
 <include name="**/*.xml"/>
 </fileset>
 </delete>
 <delete>
 <fileset dir="${WEBINF}">
 <include name="**/cmp-xdoclet.ejb"/>
 </fileset>
 </delete>
 <delete>
 <fileset dir="${WEBINF}">
 <include name="**/resin.ejb"/>
 </fileset>
 </delete>
 </target>
 <target depends="init">
 <javac destdir="${dest}" debug="true" deprecation="true">
 <src location="${gen.src}" />
 <src location="${src}" />
 <classpath ref />
 </javac>
 </target>
 <target depends="init,compile">
 <war destfile="${output}/war/${app}.war" webxml="${WEBINF}/web.xml">
 <fileset dir="${docroot}">
 <exclude name="**/build.xml" />
 <exclude name="**/*.bat" />
 <exclude name="**/build.properties" />
 <exclude name="**/web.xml" />
 <exclude name="**/*.nbattrs" />
 <exclude name="**/*.java"/>
 <exclude name="**/*.class"/>
 <exclude name="**/*.sql"/>
 <exclude name="**/*.bat"/>
 </fileset>
 <lib dir="${WEBINF}/lib">
 <exclude name="jdbc1.jar"/>
 </lib>
 <classes dir="${WEBINF}/classes">
 <exclude name="**/*.java"/>
 <exclude name="**/*.nbattrs" />
 </classes>
 </war>
 </target>
 <target depends="package">
 <copy file="${output}/war/${app}.war" todir="${webapps}" />
 </target> <target >
 <taskdef
 
 classname="xdoclet.modules.ejb.EjbDocletTask"
 classpathref="xdocpath"
 />
 <ejbdoclet
 ejbspec="2.0" mergeDir="${src}"
 destDir="${gen.src}"
 >
 <fileset dir="${src}">
 <include name="ejb/*Bean.java" />
 </fileset>
 <localinterface/>
 <localhomeinterface />
 <remoteinterface/>
 <homeinterface /> <entitypk/>
 <deploymentdescriptor destdir="META-INF" destinationFile="ejb-jar.xml" validatexml="true" />
 <deploymentdescriptor destdir="${WEBINF}" destinationFile="cmp-xdoclet.ejb" validatexml="true" />
 <resin-ejb-xml destDir="${WEBINF}"/>
 </ejbdoclet>
 </target> <target depends="clean,ejbdoclet,compile,package,deploy" />
</project>


JaVa
   
Comments