ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

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.

Pages: 1, 2

Next Pagearrow