ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Quick and Easy Custom Templates with XDoclet

by Jason Lee
05/04/2005

Recently with my developer friends, there's been much talk about various other frameworks versus Java. It seems that in some circles (I won't go into what circles, but a quick Googling will pull up plenty of matches), Java is becoming a tool for the old guard of the Web for a lot of reasons, one of which is lines of code (LOC). While some of the arguments of Java needing twice as much code as another language to get the same thing done may be accurate, I personally feel LOC is only an issue if you're spending significant amounts of time doing things you might not have to do. Like coding all of your beans by hand, for instance. Or you could be doing that, plus adding all of your framework XML definition entries by hand as well! I'll admit, I was.

The other day I was playing with Ruby on Rails, and one of the things I liked about it was the ability to easily generate complete code, as well as stub code, from simple scripts. This started me thinking about my current project and how effective I am during my coding day. The "how much work do I get done versus how much time do I spend trying to get work done" thought has been key in my mind more and more over the past few weeks. While frameworks exist to make our jobs seem easier--I use the Spring framework, for instance--it can sometimes seem like it still takes too long to just do one page or module for a web app. So after some thought, I decided to do something about it.

I had known about XDoclet for years and even played around with it when it first came out. But I didn't give it much long-term consideration, as I was not big on code generation back then. I wanted to really understand how things worked "under the hood," so I spent a long time coding everything by hand, including config and deployment files. However, today I see two main benefits for code generation:

  1. When you want to learn something, it can be very helpful. For instance, when I picked up Hibernate last year, I already had existing tables and didn't want to deal with the trial and error of trying to figure out how things worked; nor did I want to spend a great deal of time pouring over documentation. With a handy tool called Middlegen, I just told it to look at my database and generate the code for me. Now, it's fairly easy to generate my mapping files by hand, but Middlegen's help was crucial in saving me time and pointing me in the right direction.
  2. After you have concepts and the architecture in your project or framework pretty flushed out, it can become tedious to create beans, controllers, services and DAOs by hand every time something new needs to be built out. Ideally, you would just call some Ant task like gencontrollers or genmodules to generate the proper controller code or an entire module containing all of your beans, controllers, services, and DAOs.

Using XDoclet to Ease the Pain

Based on past experience, I wasn't sure how XDoclet was going to help me. I was looking for a tool that would allow me to spend the least amount of time for the maximum amount of code. Most of the examples I had seen on the XDoclet site and other sites all dealt with the generation of EJBs, which is what XDoclet was originally designed to do. However, I don't have any EJBs in my project, and in fact, Spring is all about using POJOs, so another EJB tool was not what I was looking for. And while XDoclet does have some Spring tags that it recognizes, I was still looking for something a little different.

Then I stumbled upon templates. Templates are a way to extend the functionality of XDoclet to better match your code-generation needs. For instance, if you have a particular type of class that doesn't quite fit in with anything the XDoclet offers out of the box (in my case, these were custom controller classes), then it's fairly easy to add your own template files using XML markup only! Ideally, I just wanted to define a class with a minimal set of properties and generate not only my controller code, but additional XML that would be required by Spring. After some tweaking, I was pleased with the results. Below is an annotated pseudo-code test controller class. Long lines are truncated with a \ character.

TestController.java

1.      package com.mytest.account.controller;
2.      
3.      /**
4.       * @mytest.controller mapping="/acct.ctrl"
5.       * @mytest.controller actionmethod="create"
6.       * @mytest.controller actionmethod="read"
7.       * @mytest.controller actionmethod="update"
8.       * @mytest.controller actionmethod="delete"
9.       */
10.     public class TestController {
11.     
12.         /**
13.          * @mytest.controller property="privateView" \
           propertyValue="priv1" propertyMethod="PrivateView"\
           propertyType="String"
14.          */
15.         private String privateView = null;
16.     
17.         /**
18.          * @mytest.controller property="publicView" \
         propertyValue="priv2" propertyMethod="PublicView" \
         propertyType="String"
19.          */
20.         public String publicView = null;
21.     }

The first thing that you might notice are the custom namespace @mytest.controller tags. This is how XDoclet will know what to look for when generating specific sections of our class. The positions of these tags are very important. Some appear above the class keyword and others appear right above property declarations. XDoclet uses tags based on these positions to populate Class or Field objects that we'll use in our template. This is an important distinction, as we'll see later.

Looking at the above code we want to do the following:

  1. Generate a Spring mapping entry for the SimpleUrlHandlerMapping, which could be declared in a mytest-servlet.xml file (the Spring equivalent of the Struts struts-config.xml file).
  2. The actionmethod parameter will allow us to specify a method name used in the controller, which extends the Spring MultiActionController (in Struts, this would be a DispatchAction). In our case, these will be CRUD (Create, Read, Update, Delete) method names. XDoclet will iterate over multiple parameters with the same name, which will allow our template to output n number of methods.
  3. The property tag lets us name a typical bean property, like you would normally do in XDoclet, but since we're using a custom template tag here, we're adding a few more attributes. The propertyValue is the value assigned to that property in the mytest-servlet.xml file. For our custom template, we're wiring up the private and public JSP views for this controller, which I usually call privateView and publicView--one for subscribers, and one for everyone else. The propertyMethod will give us an easy way to output the method name this property belongs to without trying to jump through hoops to adhere to the bean-method-naming convention. And the propertyType attribute specifies what object type we'll be getting and setting for our method.

It could be argued that I could have placed all of my property definitions at the class level instead of the field level. This is true; however, that would also entail extra code on the template side to make sure I'm picking up the correct properties value, method, and type attributes when generating a method. I felt it was quicker and easier to use the XDoclet Field class rather than adding more complicated code to my template.

The Template

Now it's time to get our hands dirty. As I mentioned before, the reason I chose XDoclet to do my code generation was the fact that I could define a custom template (not some custom class or more code) and use XDoclet XML tags to fill in the blanks for me. Below are the relevant portions of the template we want to generate. Long lines are truncated what a \ character.

MultiController.xdt

1.      package <XDtPackage:packageName/>;
2.      import javax.servlet.http.HttpServletRequest;
3.      import javax.servlet.http.HttpServletResponse;
4.      
5.      import org.apache.commons.logging.Log;
6.      import org.apache.commons.logging.LogFactory;
7.      import org.springframework.validation.BindException;
8.      import org.springframework.web.servlet.ModelAndView;
9.      
10.     import com.mytest.system.spring.BaseController;
11.     
12.     /**
13.      * MultiAction controller class for \
     <XDtClass:className/>.
14.      *
15.      * @author
16.      * @since
17.      * @version $Id$
18.      */
19.     public class <XDtClass:className/> \
    extends BaseController {
20.     
21.     // CUT AND PASTE THIS INTO THE mytest-servlet.xml \
    FILE
22.     //
23.     //   <!-- ==== <XDtClass:className/> \
    Bean Definition ==== -->
24.     //  <bean id="contr<XDtClass:className/>" \
        class="<XDtPackage:packageName/>.<XDtClass:className/>">
25.     //    <property name="methodNameResolver" \
          ><ref bean="actionResolver"/></property>
26.     <XDtField:forAllFields>
27.         <XDtField:ifHasFieldTag tagName="mytest.controller">  
28.     //    <property name="<XDtField:fieldTagValue \
          tagName="mytest.controller" \
          paramName="property"/>"><value><XDtField:fieldTagValue \
         tagName="mytest.controller" \
         paramName="propertyValue"/></value></property>
29.         </XDtField:ifHasFieldTag>
30.         </XDtField:forAllFields>
31.     //  </bean>
32.     //
33.     //  === PUT THIS UNDER THE URL MAPPINGS SECTION ===
34.     //  <prop key="<XDtClass:classTagValue \
        tagName="mytest.controller" \
        paramName="mapping"/>">contr<XDtClass:className/></prop>
35.     //
36.     
37.         protected final Log log = LogFactory.getLog(getClass());
38.     
39.        <XDtField:forAllFields>
40.         <XDtField:ifHasFieldTag tagName="mytest.controller">  
41.             public <XDtField:fieldTagValue \
        tagName="mytest.controller" \
        paramName="propertyType"/> \
        get<XDtField:fieldTagValue \
        tagName="mytest.controller" \
        paramName="propertyMethod"/>(){
42.                     return <XDtField:fieldTagValue \
        tagName="mytest.controller" \
            paramName="property"/>;
43.             }
44.     
45.             public void set<XDtField:fieldTagValue \
        tagName="mytest.controller" \
        paramName="propertyMethod"/>\
        (<XDtField:fieldTagValue \
        tagName="mytest.controller" \
        paramName="propertyType"/> value) {
46.             <XDtField:fieldTagValue \
        tagName="mytest.controller" \
        paramName="property"/> = value;                      
47.             }
48.        </XDtField:ifHasFieldTag>
49.       </XDtField:forAllFields>
50.     
51.         public ModelAndView init(HttpServletRequest request, \
        HttpServletResponse response)
52.             throws ServletException, IOException {
53.             if(log.isDebugEnabled()) log.debug("entered");
54.              // YOUR INIT CODE GOES HERE
55.             if(log.isDebugEnabled()) log.debug("exited");
56.             // REPLACE THIS HERE IN YOUR CODE
57.             return null;
58.         }
59.     
60.        <XDtClass:forAllClassTags \
       tagName="mytest.controller">
61.         <XDtClass:ifHasClassTag \
       tagName="mytest.controller" \
        paramName="actionmethod">
62.         public ModelAndView <XDtClass:classTagValue \
        tagName="mytest.controller" \
        paramName="actionmethod"/>(\
        HttpServletRequest request, HttpServletResponse \
        response)
63.             throws ServletException, IOException {
64.             if(log.isDebugEnabled()) log.debug("entered");
65.     
66.             if(log.isDebugEnabled()) log.debug("exited");
67.             // RETURN THE PROPER VIEW HERE
68.             return null;
69.         }
70.     
71.         </XDtClass:ifHasClassTag>
72.        </XDtClass:forAllClassTags>
73.     
74.         // Your (other) multiaction methods go here
75.     
76.     } // <XDtClass:className/>

Phew! But when it's all done, I have a controller class that contains the proper XML snippets for the Spring mytest-servlet.xml bean definition and URL mappings, the proper get/set methods for view properties, and my multi-action methods, complete with debug logging statements. 76 lines of code from 21, including the XML (which will be cut out), can save significant typing time and also eliminate bugs.

Note also the use of the XDtField and XDtClass tags. It should be pretty clear now why we placed the various tag values where we did. All of our class-level tags either defined class methods or XML config file values, which the field level tags used to generate our basic getters and setters. Like I said before, our use of the field tags here are a little different, as we're also using them to define some of the controller mapping values in our mytest-servlet.xml file for Spring. When our controller stars up, Spring will inject the privateView and publicView methods with their proper view values.

Putting It Together

The last piece of the puzzle is how to put this all together and get our hands on the generated source file. This is where Ant comes in. We'll be doing three things:

  1. Create a directory, separate from our code base, where we'll keep the pseudo-source and auto-generated source files.
  2. Create a directory for our templates.
  3. Create an Ant task for the code generation.

On my development machine, I have the following directory structure:

trunk
  |
  +----+- src
       +- xdoclet
            |
            +- build
            +- src
                +- com/mytest/account/controller
            +- template

At first, this may seem a little confusing, with two src directories. However, I feel there is good reason for this, as I have a separate src directory under the xdoclet directory that allows me to keep separate all of the pseudocode and generated code. After I have run the Ant task, it's just a matter of copying the file over to the main src directory (on the same level of the trunk as the xdoclet directory) and adding in the implementation code. For the purpose of this article, I'm keeping these things separate from the source code base, which should prevent any build issues that might creep in if we try to integrate everything at once.

Based on the above directory structure, this is where our two files should live:

trunk/src/xdoclet/src/com/mytest/account/controller/TestController.java

trunk/src/xdoclet/template/MultiController.xdt

The .java file will need the package to match the directory structure for XDoclet to be able to pick it up and run the template against it. I found XDoclet only had the log4j debugging levels set to INFO and so, when I did not have the .java file in the correct tree, XDoclet would not display any error messages; nor would it find and generate the file. It wasn't until I went into the .jar and modified the log4j.properties file so that debugging was turned on that I got the proper debug messages saying there were no files to process. Technically, this wasn't an error, but it kept me scratching my head for some time, so be aware of this.

The following Ant task will use the above tree and place the generated code in the proper directory.

1.      <!-- ========Xdoclet Target ======== -->
2.
3.  <target name="xdoclet">
4.
5.  <path id="xdoclet.classpath">
6.    <fileset dir="${xdoc.home}/lib"> 
7.      <include name="*.jar"/> 
8.    </fileset> 
9.  </path>
10.
11.  <echo>+------------------------------------+</echo>
12.  <echo>|Generating sources from xdoclet tree|</echo>
13.  <echo>+------------------------------------+</echo>
14.
15.  <taskdef name="doclet" \
     classname="xdoclet.DocletTask" \
     classpathref="xdoclet.classpath" />
16.  <doclet destdir="${jxdoc.dir}/build"
17.        verbose="true">
18.    <fileset dir="${jxdoc.dir}/src">
19.      <include name="**/*Controller.java" />
20.    </fileset>
21.    <template
22.       templateFile="${jxdoc.dir}/template/MultiController.xdt"
23.       destinationFile="{0}.java"
24.       subTaskName="Create Controller file(s).." />
25.  </doclet>
26.
27.  </target>

A few settings will be required to run this.

  1. On line 6, xdoc.home should point to your XDoclet install directory.
  2. Line 22 defines the location and name of our template file.
  3. Line 24 specifies the task name message that XDoclet will send to the console when the doclet task is running. This is for convenience only.

You can run the task by issuing the following command: ant xdoclet.

If all goes well, you should see the usual Ant BUILD SUCCESSFUL message. Then, under the xdoclet/build/com/mytest/account/controller directory, your new file should appear, full of the necessary skeleton properties and methods.

This template approach can also be adapted to generate not only controllers, but beans, forms, services, etc. In the xdoclet target, you would just have to add a new taskdef for each object type you want to generate and provide a different template. For example, if we want to generate service objects, we could use the following taskdef:

1.  <taskdef name="servicedoclet" \
    classname="xdoclet.DocletTask" \
    classpathref="xdoclet.classpath" />
2.  <servicedoclet destdir="${jxdoc.dir}/build"
3.        verbose="true">
4.    <fileset dir="${jxdoc.dir}/src">
5.      <include name="**/*Service.java" />
6.    </fileset>
7.    <template
8.       templateFile="${jxdoc.dir}/template/Service.xdt"
9.       destinationFile="{0}.java"
10.       subTaskName="Create Service files(s).." />
11.  </doclet>

Above, we see that we've changed the name of the taskdef to servicedoclet and also the include fileset property from *.Controller.java to *.Service.java, which will only look for files ending in "Service.java." On line 8, we are telling XDoclet to use the Service.xdt template. So we see that it's fairly trivial to add on special code generators once we have the initial setup going. It is entirely possible (in fact, this is what I do) to generate a few templates and, under the xdoclet task, have it look through the source tree for each object type and generate everything at once.

A word about Maven 2.0 and Archetypes: Maven 2.0 will contain a mechanism called Archetypes which allow for template-driven project code generation. While implemented differently--using Velocity for the templates--than what we have done above, it will basically accomplish the same things, but on a larger, project scale. I have tried Maven a few times and I can appreciate its scope. However, I feel it is much more a part of a larger picture and would require more developer effort and time (in the beginning, of course) to integrate than the example above. But it does address one of my initial issues, that I liked the fact that Rails worked with the developer on a project level. Maven, too, will operate on that project level, saving developers time by auto-generating the needed code based on set config parameters. This can only be a positive thing. For existing projects, however, the approach discussed here will probably be faster to implement and have less of a structural impact.

Conclusion

To recap, here's what we've accomplished:

  1. Created a custom pseudo-Java file containing all of the methods and properties to not just generate a class, but also generate the necessary XML for our framework config files. In our case, we defined a custom controller that could be used in the Spring web framework.
  2. Defined a template file that contained the static elements of our class and incorporated XDoclet XML tags, which provided the dynamic class code at run time.
  3. Set up a quick directory structure and Ant task that will call XDoclet to generate our skeleton controller class.

This only scratches the surface of XDoclet templates. We've seen just one small, yet powerful, example of how templates can help you not just become a more efficient programmer, but also a smarter one. I hope it will save you as much time as it has me.

Resources

Jason Lee has been developing web apps for the past ten years and currently is VP of technology at a small startup.

Java Extreme Programming Cookbook

Related Reading

Java Extreme Programming Cookbook
By Eric M. Burke, Brian M. Coyner

Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.