Doclet your servlet!

Write better documentation with ServletDoclet

So you've written that killer servlet, and now it's been discovered. Congratulations!

But alas, your inbox is flooded with messages: What do the parameters mean? What beans does the servlet put in the session, and how can I access those beans from a JavaServer Page (JSP)? What URL variants can I use to invoke the servlet? And how can I tell which JSP is going to be called to render the response? Too bad you didn't write any documentation because now, rather than sitting back and reaping the rewards of your accomplishment, you're getting carpal tunnel syndrome dealing with that email flood. Doclets to the rescue!

How can doclets help? With great wisdom, the Java team understood that developers don't like to produce documentation because writing documentation takes them away from coding. The JDK team made complete documentation much easier to produce and maintain by integrating documentation production with the routine production of commented code. They produced an elegant documentation engine, JavaDoc, which scans code for the comments that developers have always written at the beginning of each class and method. Upon completing the scan, JavaDoc outputs a set of HTML pages that present the information derived from the scanned code and comments. Further, JavaDoc looks for a special set of tags within the comments and uses them to structure the resulting documentation into important categories, such as method parameters or hyperlinks to a related method.

With the onset of Java 1.2, JavaDoc became an extensible tool. Developers could produce alternative forms of documentation by writing a Doclet -- a class that takes as input the documentation-related information that JavaDoc can extract from a set of files, and writes documentation files in a format specified by the developer. Most example doclets stay pretty close to the original JavaDoc mission. For example, Sun provides a doclet that can write code documentation in RTF (rich text format, which most word processors can read) instead of HTML format. Some more adventuresome doclets actually generate new Java code files; for example, the Swing team internally uses a special doclet to automatically create BeanInfo classes from Bean classes.

In this article, I introduce another kind of doclet extension; producing documentation intended to be read by HTML or JSP designers instead of programmers. In particular, the ServletDoclet class described below produces HTML files that organize the information that any Webpage author would want to know about a servlet. How do I invoke the servlet from a link or a form? What parameters can I use, and what do they do? What session beans change state as a result of calling that servlet? What JSPs are associated with the servlet and render its output? You cannot find that information in standard JavaDoc documentation for a servlet class, and Webpage authors shouldn't have to scan your source code to deduce what parameters it takes.

Using the techniques described below, you can provide solid documentation to your Webpage authors with just a simple extension to your normal coding practice. By adding a specific set of tags to your servlet's JavaDoc comments, and running the ServletDoclet, you can produce comprehensive documentation for your servlet at the touch of a key.

How doclets work: A quick overview

Using a doclet with JavaDoc requires only a small change in the command line. In the normal JavaDoc command line, you indicate the files or packages you wish to document, a path upon which to find other related source files (the -sourcepath), and an output directory. To use a doclet, you simply add -doclet and the fully qualified class name. For example, the ServletDoclet can be invoked on the BookmarkServlet.java file with this command:

javadoc -doclet javaworld.ServletDoclet -d docs -sourcepath src
     src/javaworld/BookmarkServlet.java

In addition, for documenting servlets, you should have the Servlet APIs available to JavaDoc for parsing (otherwise, you will get warnings from JavaDoc). You can download the source code for the javax.servlet and javax.servlet.http packages from Apache's Jakarta project, and then put the source into the -sourcepath indicated on the command line.

JavaDoc execution

First, regardless of which doclet you use, JavaDoc scans all the targeted files or packages and produces an internal representation of the classes and comments contained therein. Each class is represented by an instance of ClassDoc, which contains information about the class. Every method is represented by an instance of MethodDoc as well. When JavaDoc completes its scan, each ClassDoc and MethodDoc contains information representing the comments and tags found relating to the class or method. That bundle of ClassDoc instances is collected in a RootDoc, along with information about any additional, custom command-line arguments.

Second, JavaDoc loads the requested doclet and calls its start() method, passing in the RootDoc (which contains all the ClassDoc and MethodDoc instances, as well as the command-line options). The doclet then writes out documentation using whatever techniques it likes, extracting information from the RootDoc, ClassDoc, and MethodDoc instances along the way. Generally speaking, that involves creating PrintStream instances that write to disk, and writing out formatted content information to them based on the ClassDoc and MethodDoc instances.

The ServletDoclet approach

This article follows my prior article about using reflection with servlets "Untangle Your Servlet Code with Reflection"). In that article, I described a technique for using reflection to map URLs onto methods within the servlet that execute a particular behavior of that servlet. Those methods are called controllers, following the model-view-controller (MVC) convention. ServletDoclet detects controller methods and their URLs, and produces documentation for each.

Before diving into code, it's worthwhile to sketch the ServletDoclet approach at a high level. First, ServletDoclet specifies an additional set of tags, which you can use in ServletDoclet comments. The following table lists those tags and what they mean.

TagUsage
@baseURLProvides the URL that invokes the servlet.
@requestParameterThe name of a parameter that can be included in a GET or POST to the servlet, followed by a comment describing the meaning of the parameter.
@requestBeanDocuments a bean that passed to the JSP with a request level scope. Format is bean class, bean name, and then explanatory comment.
@sessionBeanDocuments a bean that is passed to the JSP with a session level scope. Format is bean class, bean name, and then explanatory comment.
@jspA JSP filename to which that servlet forwards the request to produce its output. A comment can follow the filename to explain the conditions under which that JSP or an alternative is called, and what the JSP basically does (e.g., presents a form to the user to edit his or her profile).

Second, ServletDoclet takes the ClassDoc passed to it by JavaDoc and figures out which classes correspond to servlets. It then creates an instance of ServletDoc for each servlet, with instances of ControllerDoc for each controller within the servlet. (Note that the ServletDoclet class is the overall doclet, whereas the ServletDoc instance is used to represent an individual servlet's documentation.)

Finally, ServletDoclet creates an instance of a formatting class called HtmlDocletWriter, and passes an array of ServletDoc instances into it. The HtmlDocletWriter instance then writes out the actual documentation in HTML format.

The code presented here is based on an earlier version produced collaboratively with Wei Cao, who was an intern at SRI International, my current employer, in the summer of 2000. You can download the complete source code from Resources.

Below is an example of using the tags to comment a particular controller method within a servlet class:

/** adds a url to the bookmark list
   @requestParameter url the url to add
   @requestParameter name the name to associate with the added url
   @requestBean String message will say "added 1 bookmark" 
   @requestBean String mode will be "view" when called by this URL
   @requestBean BookmarkList list contains the current bookmarks 
   @jsp /javaworld/bookmarks.jsp displays the bookmark list
*/
public void add(HttpServletRequest request, 
                 HttpServletResponse response) {
} 

The resulting documentation looks like this:

<insert ServletDocletSnippet.html here, perhaps as an image from a browser ??>

For a full example of a commented servlet, see BookmarkList.java. For a full example of a resulting HTML page, see BookmarkListInfo.html.

Getting into the code

The entry point from JavaDoc into any doclet is the static start() method. In the case of ServletDoclet, the start() method reads options from the command line (to be discussed in more detail later), constructs ServletDoc instances for each servlet ClassDoc, and then writes an HTML document for each ServletDoc. Returning "true" indicates success.

public static boolean start(RootDoc root){
    readOptions(root.options());
    ServletDoc[] docs = constructServletDocs(root.classes());
    for(int i = 0; i < docs.length; ++i) {
        writeDoc(docs[i]);
    }
    return true;
}    

An array of ServletDoc is constructed by iterating through the given array of classes and determining which are servlets. The isServlet method (not shown) works recursively through the superclasses of the given class until it finds either the Servlet class or a class with no superclass. In the former case, that class extends Servlet, so the method returns true. In the latter case, it returns false.

static ServletDoc[] constructServletDocs(ClassDoc[] classes) {
    Vector vector = new Vector();
    for (int i=0; i < classes.length; i++) {
        if (isServlet(classes[i]) ){
             vector.add(new ServletDoc(classes[i]));                           
        }
    }
    ServletDoc[] array = new ServletDoc[vector.size()];
    vector.copyInto(array);
    return array;
}

Representing a servlet with ServletDoc

To create reasonable documentation, you only need to know a few things about the servlet itself. Of course, it's good to know the servlet's name and description. The name is taken from the class name, and the description from the JavaDoc comment for the class. The ServletDoc class maintains a member variable referencing the ClassDoc from which it is derived, and can easily get that information from the ClassDoc's name and commentText() methods. In addition, you will want to know the baseURL that is used to invoke that servlet on the Web server. Since the Web server might have varying domain names, you report this as a relative URL, starting with a "/".

ServletDoc discovers its baseURL from the @baseURL tag in the class comment. The Doclet API makes it easy to extract that information, given the ClassDoc. Calling the tag() method with the name of the desired tag (without the "@" prefix) results in an array of zero or more tags. Each tag's text() method returns the text associated with the tag. Note that the full baseURL() method, below, checks for a missing tag and returns the baseURL "/servletName" in that case.

public String baseURL() {
    if (mBaseURL == null) {
        Tag[] tag = mClassDoc.tags(ServletDoclet.sBaseURLTag);
        mBaseURL = tag.length == 0 ? "/" + name() : tag[0].text();
    }
    return mBaseURL;
}

The final useful piece of information about a servlet is its list of controller methods; a controller method is a method that takes a servlet request and a servlet response as its parameters. ServletDoc's initialize() method constructs a Vector of ControllerDoc instances to represent each controller method it finds. It does so by iterating through the methods and making a new ControllerDoc for each method it finds with the right parameters, as shown below.

public Enumeration controllers() {
    return mControllers.elements();
}
    
private void initialize() {
    mControllers = constructControllers();
}
    
private Vector constructControllers() {
    Vector results = new Vector();
    MethodDoc[] methods = mClassDoc.methods();
    for(int i = 0; i < methods.length;++i) {
        if (isControllerMethod(methods[i] )) {
            results.addElement(new ControllerDoc(methods[i], baseURL()));
        }
    }
    return results;
}
private boolean isControllerMethod(MethodDoc methoddoc) {
    Parameter[] params = methoddoc.parameters();
    return params.length == 2 
                && params[0].type().typeName().equals("HttpServletRequest") 
                && params[1].type().typeName().equals("HttpServletResponse");
}

A ControllerDoc wraps a particular MethodDoc, providing easy access to the information useful for servlet documentation. Basic useful information includes the name, description, and URL for the controller method, extracted in similar fashion as the related ServletDoc() methods. In addition, ControllerDoc provides methods for getting an array of tags for requestParameters, requestBeans, sessionBeans, and jsps -- which work in a similar fashion to the baseURL() method described above, and thus will not be further explained here; the ControllerDoc.java file provides full details.

Now you have seen how information relevant to servlet documentation is extracted from the ClassDoc. The next step is to write that information into an HTML file.

1 2 3 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more