Building the EJB Tier


The ToDo application looks like any other J2EE application. It consists of an EJB JAR file that contains the EJBs, and a WAR file that provides the web application code. Both of these are wrapped up in an EAR file that comprises the entire enterprise application. Since the web tier depends on the EJB tier, we'll start there.

How do I do that?

The EJB tier consists of two local CMP entity beans, TaskBean and CommentBean, and one local session bean, TaskMasterBean. You can find the source code for all the beans in the src/com/oreilly/jbossnotebook/todo/ejb directory.

Notice that there are only three source files for the three enterprise beans, instead of the dozen or so you might normally expect to write. XDoclet will do the heavy lifting and generate all the required J2EE interfaces, as well as a host of other supporting classes.


Note: See the build.xml file for the taskdef operations required to load the XDoclet tasks.

Tip: XDoclet isn't just a great tool for developing EJB applications. It's used to develop the JBoss server itself. You'll find XDoclet attributes throughout the JBoss code tree.

The build.xml file contains the ejbdoclet target, which is responsible for invoking the XDoclet-provided ejbdoclet task. This code-generation step needs to take place right before the compile step.

     <target name="ejbdoclet" depends="init">         <mkdir dir="dd/ejb" />         <ejbdoclet destdir="${gen.src.dir}" ejbSpec="2.1">             <fileset dir="${src.dir}">                 <include name="**/*Bean.java"/>             </fileset>             <deploymentdescriptor destdir="dd/ejb"/>             <homeinterface/>             <remoteinterface/>             <localinterface/>             <localhomeinterface/>             <utilobject includeGU cacheHomes="true" />             <valueobject pattern="{0}"/>             <entitycmp/>             <session/>         </ejbdoclet>     </target> 


Note: It would be nice if we didn't need to generate so many classes, but all things considered, it isn't so bad when you are using XDoclet.

Each subtask inside the ejbdoclet task represents one unique code-generation task XDoclet provides. We will be generating nine different types of classes.

As an example of what XDoclet does, take a look at one of the entity beans. Notice in Example 3-1 that all the metadata and bean configuration are done using special tags inside of Javadoc comments.

Example 3-1. The TaskBean entity bean
 package com.oreilly.jbossnotebook.todo.ejb; import java.util.Date; import java.util.Set; import javax.ejb.CreateException; import javax.ejb.EntityBean; /**  * Entity bean representing a blog entry.  *  * @ejb.bean name="Task"  *           type="CMP"  *           cmp-version="2.x"  *           view-type="local"  *           primkey-field="id"  *  * @ejb.finder signature="java.util.Collection findAll(  )"  *             query="SELECT OBJECT(t) FROM Task AS t"  *  * @ejb.finder  *    signature="java.util.Collection findTasksForUser(java.lang.String user)"  *    query="SELECT OBJECT(t) FROM Task AS t WHERE t.user = ?1"  *  * @ejb.value-object name="Task"  */ public abstract class TaskBean     implements EntityBean {     /** @ejb.create-method */     public String ejbCreate(String user, String name)         throws CreateException     { ...     }     public void ejbPostCreate(String name, String user)         throws CreateException     {     }     /**      * @ejb.pk-field      * @ejb.persistence      * @ejb.interface-method      */     public abstract String getId(  );     public abstract void setId(String id);     /**      * @ejb.persistence      * @ejb.interface-method      */     public abstract String getName(  );     /** @ejb.interface-method */     public abstract void setName(String name);     /**      * @ejb.persistence      * @ejb.interface-method      */     public abstract String getUser(  );     /** @ejb.interface-method */     public abstract void setUser(String topic);     /**      * @ejb.persistence      * @ejb.interface-method      */     public abstract Date getStartedDate(  );     /** @ejb.interface-method */     public abstract void setStartedDate(Date date);     /**      * @ejb.persistence      * @ejb.interface-method      */     public abstract Date getCompletedDate(  );     /** @ejb.interface-method */     public abstract void setCompletedDate(Date date);     /**      * @ejb.interface-method      *      * @ejb.relation name="task-comment"      *               role-name="task-has-comments"      * @ejb.value-object             aggregate="com.oreilly.jbossnotebook.todo.ejb.Comment"      *      aggregate-name="Comment"      *      members="com.oreilly.jbossnotebook.todo.ejb.CommentLocal"      *      members-name="Comments"      *      relation="external"      *      type="java.util.Set"      *      */     public abstract Set getComments(  );     /** @ejb.interface-method */     public abstract void setComments(Set comments);     /** @ejb.interface-method */     public abstract Task getTask(  );     /** @ejb.interface-method */     public abstract void setTask(Task task); } 


Note: The XDoclet attributes closely resemble the annotations model that will be used in EJB3.

Note: Container-managed persistence is much simpler when you don't have to worry about the deployment descriptor.

Everything related to this bean is declared in the bean class. The @ejb.create-method and @ejb.finder attributes tell XDoclet how to generate the local home interface. Example 3-2 shows the generated interface.

Example 3-2. The generated local home interface
 package com.oreilly.jbossnotebook.todo.ejb; /**  * Local home interface for Task.  */ public interface TaskLocalHome    extends javax.ejb.EJBLocalHome {    public static final String COMP_NAME="java:comp/env/ejb/TaskLocal";    public static final String JNDI_NAME="TaskLocal";    public com.oreilly.jbossnotebook.todo.ejb.TaskLocal           create(java.lang.String user , java.lang.String name)       throws javax.ejb.CreateException;    public java.util.Collection findAll(  )       throws javax.ejb.FinderException;    public java.util.Collection findTasksForUser(java.lang.String user)       throws javax.ejb.FinderException;    public com.oreilly.jbossnotebook.todo.ejb.TaskLocal           findByPrimaryKey(java.lang.String pk)       throws javax.ejb.FinderException; } 


Note: The localhomeinterface subtask generates local home interfaces.

Note: The localinterface subtask generates local interfaces.

The local interface is based on the signatures of methods marked with @ejb.interface. Example 3-3 shows the generated local interface.

Example 3-3. The generated local interface
 package com.oreilly.jbossnotebook.todo.ejb; /**  * Local interface for Task.  */ public interface TaskLocal    extends javax.ejb.EJBLocalObject {     public java.lang.String getId(  );     public java.lang.String getName(  );     public void setName(java.lang.String name);     public java.lang.String getUser(  );     public void setUser(java.lang.String topic);     public java.util.Date getStartedDate(  );     public void setStartedDate(java.util.Date date);     public java.util.Date getCompletedDate(  );     public void setCompletedDate(java.util.Date date);     public java.util.Set getComments(  );     public void setComments(java.util.Set comments);     public com.oreilly.jbossnotebook.todo.ejb.Task getTask(  );     public void setTask(com.oreilly.jbossnotebook.todo.ejb.Task task); } 


Note: The valueobject subtask generates the value objects.

Entity beans aren't meant to be passed all the way up the web tier. We need to shuttle the entity values in and out through a value object. The @ejb.value-object attributes determine which fields and relations should be represented on the value object that the application code will use. Example 3-4 shows the generated task value object.

Example 3-4. The generated value object
 package com.oreilly.jbossnotebook.todo.ejb; /**  * Value object for Task.  *  */ public class Task    extends java.lang.Object    implements java.io.Serializable {    // [... private instance variables ...]    public Task(  ) {  }    public Task(java.lang.String id,                java.lang.String name,java.lang.String user,                java.util.Date startedDate,                java.util.Date completedDate)    {... }    public Task( Task otherValue ) { ... }    public java.lang.String getPrimaryKey(  ) { ... }    public void setPrimaryKey( java.lang.String pk ) { ... }    public java.lang.String getId(  ) { ... }    public void setId(java.lang.String id) { ... }    public boolean idHasBeenSet(  ) { ... }    public java.lang.String getName(  ) { ... }    public void setName(java.lang.String name) { ... }    public boolean nameHasBeenSet(  ) { ... }    public java.lang.String getUser(  ) { ... }    public void setUser(java.lang.String user) { ... }    public boolean userHasBeenSet(  ) { ... }    public java.util.Date getStartedDate(  ) { ... }    public void setStartedDate(java.util.Date startedDate) { ... }    public boolean startedDateHasBeenSet(  ) { ... }    public java.util.Date getCompletedDate(  ) {... }    public void setCompletedDate( java.util.Date completedDate ) {... }    public boolean completedDateHasBeenSet(  ) {... }    public java.util.Set getAddedComments(  ) { return addedComments; }    public java.util.Set getOnceAddedComments(  ) { return onceAddedComments; }    public java.util.Set getRemovedComments(  ) { return removedComments; }    public java.util.Set getUpdatedComments(  ) { return updatedComments; }    public void setAddedComments(java.util.Set addedComments) { ... }    public void setOnceAddedComments(java.util.Set onceAddedComments) { ... }    public void setRemovedComments(java.util.Set removedComments) { ... }    public void setUpdatedComments(java.util.Set updatedComments) { ... }    public com.oreilly.jbossnotebook.todo.ejb.Comment[  ] getComments(  ) { ... }    public void setComments(com.oreilly.jbossnotebook.todo.ejb.Comment[  ] Comments) {... }    public void clearComments(  ) { ... }    public void addComment(com.oreilly.jbossnotebook.todo.ejb.Comment added) {... }    public void removeComment(com.oreilly.jbossnotebook.todo.ejb.Comment removed) {... }    public void updateComment(com.oreilly.jbossnotebook.todo.ejb.Comment updated) {... }    public void cleanComment(  ) {... }    public void copyCommentsFrom(com.oreilly.jbossnotebook.todo.ejb.Task from) { ... }    public String toString(  ) { ... }    protected boolean hasIdentity(  ) { ... }    public boolean equals(Object other) { ... }    public boolean isIdentical(Object other) { ... }    public int hashCode(  ) { ... } } 


Note: Dirty checks and relation management in value objects can get very tricky.Aren't you glad you don't need to write these methods?

Note: Methods such as toString(), equals(), and hashCode() are difficult to keep in sync with changes to the entity bean. XDoclet ensures that these methods are always current.

Where do the value object instances get created? XDoclet can generate a subclass of our bean that provides the methods to create and consume the value objects, as well as the default implementations of the entity bean lifecycle methods. The CMP subclass helper gets very little attention, but it is one of the more valuable XDoclet tasks. Managing value objects is hard work. Example 3-5 shows the generated CMP subclass.

Example 3-5. The generated CMP subclass
 package com.oreilly.jbossnotebook.todo.ejb; /**  * CMP layer for Task.  */ public abstract class TaskCMP    extends com.oreilly.jbossnotebook.todo.ejb.TaskBean    implements javax.ejb.EntityBean {    public void ejbLoad(  ) { }    public void ejbStore(  ) { }    public void ejbActivate(  ) { }    public void ejbPassivate(  ) {... }    public void setEntityContext(javax.ejb.EntityContext ctx) { }    public void unsetEntityContext(  ) { }    public void ejbRemove(  )        throws javax.ejb.RemoveException    {  }    /* Value Objects BEGIN */    public void addComments(com.oreilly.jbossnotebook.todo.ejb.Comment added)        throws javax.ejb.FinderException    {... }    public void removeComments(com.oreilly.jbossnotebook.todo.ejb.Comment removed)        throws javax.ejb.RemoveException    {... }    private com.oreilly.jbossnotebook.todo.ejb.Task Task = null;    public com.oreilly.jbossnotebook.todo.ejb.Task getTask(  )    {... }    public void setTask(com.oreilly.jbossnotebook.todo.ejb.Task valueHolder)    {... }    /* Value Objects END */    public abstract java.lang.String getId(  ) ;    public abstract void setId( java.lang.String id ) ;    public abstract java.lang.String getName(  ) ;    public abstract void setName( java.lang.String name ) ;    public abstract java.lang.String getUser(  ) ;    public abstract void setUser( java.lang.String user ) ;    public abstract java.util.Date getStartedDate(  ) ;    public abstract void setStartedDate( java.util.Date startedDate ) ;    public abstract java.util.Date getCompletedDate(  ) ;    public abstract void setCompletedDate( java.util.Date completedDate ) ; } 


Note: The entitycmp task generates the CMP subclass.getTask() and setTask() perform the actual work of creating and consuming the value objects.

Finally, XDoclet generates a helpful utility class which provides convenience methods for looking up the local home interface from JNDI and for the generation of UUIDs that can be used as a surrogate key in the database. Example 3-6 shows the generated TaskUtil class, with the long UUID code omitted.


Note: The utilobject subtask generates the bean helper utility class.
Example 3-6. The generated EJB helper class
 package com.oreilly.jbossnotebook.todo.ejb; /**  * Utility class for Task.  */ public class TaskUtil {    /** Cached local home (EJBLocalHome). Uses lazy loading to obtain     * its value (loaded by getLocalHome(  ) methods).     */    private static com.oreilly.jbossnotebook.todo.ejb.TaskLocalHome                   cachedLocalHome = null;      private static Object lookupHome(java.util.Hashtable environment,                                     String jndiName, Class narrowTo)        throws javax.naming.NamingException    {... }      // UUID code deleted } 


Note: The bean gets the JNDI name to look up from the generated local home object.

We've only shown the code for TaskBean, but similar code is generated for CommentBean and TaskMasterBean. That is a lot of code--1,403 lines over 14 files, according to the wc command:

 [ejb]$ wc -l *      202 Comment.java       96 CommentCMP.java       31 CommentLocal.java       24 CommentLocalHome.java      116 CommentUtil.java      387 Task.java      169 TaskCMP.java       39 TaskLocal.java       27 TaskLocalHome.java       25 TaskMasterLocal.java       18 TaskMasterLocalHome.java       37 TaskMasterSession.java      116 TaskMasterUtil.java      116 TaskUtil.java     1403 total 


Note: Not only is it a lot of code,but also it is very repetitive code. It is a perfect target for code generation.

Note: We could have generated the Boss deployment descriptor too, but we don't need any JBoss specific configuration to make the application run.

We have one more task for XDoclet. XDoclet will generate ejb-jar.xml for us. Since the deployment descriptor is so standard, we won't show it here. Just keep in mind that XDoclet has generated the information for all the persistent fields and relationships, the queries, and all the security and transaction declarations. Where we haven't specified the details in our class, XDoclet assumes reasonable defaults.

With our entire EJB tier complete, we need to compile the code and place it, along with the deployment descriptor, into an EJB JAR file:

     [todo]$ jar tf build/jars/todo.jar     META-INF/     META-INF/MANIFEST.MF     META-INF/ejb-jar.xml     com/     com/oreilly/     com/oreilly/jbossnotebook/     com/oreilly/jbossnotebook/todo/     com/oreilly/jbossnotebook/todo/ejb/     com/oreilly/jbossnotebook/todo/ejb/Comment.class     com/oreilly/jbossnotebook/todo/ejb/CommentBean.class     com/oreilly/jbossnotebook/todo/ejb/CommentCMP.class     com/oreilly/jbossnotebook/todo/ejb/CommentLocal.class     com/oreilly/jbossnotebook/todo/ejb/CommentLocalHome.class     com/oreilly/jbossnotebook/todo/ejb/CommentUtil.class     com/oreilly/jbossnotebook/todo/ejb/Task.class     com/oreilly/jbossnotebook/todo/ejb/TaskBean.class     com/oreilly/jbossnotebook/todo/ejb/TaskCMP.class     com/oreilly/jbossnotebook/todo/ejb/TaskLocal.class     com/oreilly/jbossnotebook/todo/ejb/TaskLocalHome.class     com/oreilly/jbossnotebook/todo/ejb/TaskMasterBean.class     com/oreilly/jbossnotebook/todo/ejb/TaskMasterLocal.class     com/oreilly/jbossnotebook/todo/ejb/TaskMasterLocalHome.class     com/oreilly/jbossnotebook/todo/ejb/TaskMasterSession.class     com/oreilly/jbossnotebook/todo/ejb/TaskMasterUtil.class     com/oreilly/jbossnotebook/todo/ejb/TaskUtil.class 

What just happened?

We generated a complete EJB application, having only written just a little over 300 lines of code:


Note: Most of the code we wrote was actually just metadata.
     [ejb]$ wc -l *           87 CommentBean.java          117 TaskBean.java          129 TaskMasterBean.java          333 total 

It's easy to see why J2EE developers who don't use a tool such as XDoclet can be frustrated by the amount of code they have to write. While we won't claim to be happy that we have to generate code, it is great to have a tool such as XDoclet that can take care of the complexity.

What about...

...the JBoss deployment descriptors?

XDoclet does have support for generating JBoss deployment descriptors, but so far the application hasn't required any JBoss-specific configuration. That's because the defaults in JBoss make sense for development purposes. We'll explore many of the JBoss customizations in later chapters.



JBoss. A Developer's Notebook
JBoss: A Developers Notebook
ISBN: 0596100078
EAN: 2147483647
Year: 2003
Pages: 106

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net