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

advertisement

AddThis Social Bookmark Button

Documenting Projects with Apache Forrest Documenting Projects with Apache Forrest

by Kyle Downey
05/26/2004

One of the things that divides good from great software projects is clear, effective documentation. It's not easy to maintain, though, and even if you're determined to produce good documentation, you may find yourself bogged down in the details of formatting and publishing. Apache's Forrest lets you focus exclusively on your content, and automates the rest: nothing more, nothing less. Whether you want to document an open source project or an internal application, Forrest can help you build professional, polished documentation.

This article will give you a very basic introduction to Forrest -- just enough to get you started and give you a taste of what's possible -- to see if Forrest's for you; you can find out much more from Forrest's own documentation. We'll close out by taking a look at a small extension to Forrest to give you a sense of what it takes to customize this documentation platform using Java.

Introduction to Forrest

Forrest takes raw documentation content (usually in a simplified XML authoring format) and automagically converts it into a web site with:

  • Smart hierarchical menus
  • Named, logical links, making it easy to cross-reference without regard to the physical location of files
  • Custom headers and footers
  • "Breadcrumbs" links around the site
  • Multiple tabs
  • "Favorites" icon for IE and other supporting browsers
  • A choice of "skins" with different appearances
  • Conversion of SVG to a variety of image formats (e.g., PNG)
  • A choice of dynamic generation (run it as a web app) or static generation (run it by hand and generate HTML)
  • Links to PDF versions of pages, or even the whole site

The Forrest web site itself is a large application of Forrest, so you can get an idea of what's possible by browsing around the site.

Internally, Forrest is a sophisticated application of Cocoon. It's an XML application framework that can be used for offline web site generation and dynamic web site generation, but really it should be thought of as an XML processing pipeline. You can plug in generators (XML sources), transformers (XML-to-XML translations) and serializers (XML to other formats, such as PDF). Cocoon provides all kinds of built-in generators, transformers and serializers -- many of which Forrest uses -- but more importantly, it orchestrates them. You use XML files to define your pipeline and then execute the Cocoon engine to run it.

Related Reading

XML Hacks
100 Industrial-Strength Tips and Tools
By Michael Fitzgerald

Personally I think Forrest's great strength is the strict separation between content and view. Too often, when writing documentation in straight HTML, I found myself hung up on the details of look and feel, and maintenance was a nightmare. Changes to the overall appearance require altering all of the documentation pages. With Forrest, it's very easy to target the content change or skin/appearance configuration you want to make and leave it to the tool to reflect it throughout the site.

Forrest-Enabling a Project: A Quick Start

Forrest should win a prize for quickest and most convenient start; this is an area where many open source projects fall down. You can get from nowhere to a project with skeletal documentation with a single command. Once you've unpacked the Forrest binary distribution, all you need to do is set the environment variable FORREST_HOME and add ${FORREST_HOME}/bin to your PATH. From there, just run forrest seed in the top level directory of your project. This will execute Ant behind the scenes, using a custom build file.

This whole process will give you a default forrest.properties file in your project root directory and a src/documentation/content/xdocs directory with sample documentation in the various formats Forrest supports. It also sets up a default skin configuration in src/documentation/skinconf.xml.

From here, you can run forrest any time to re-generate your web site. It will show up under build/site and you can either open the local files in your web browser or take advantage of Forrest's online mode.

Customizing the Xdocs

The heart of the documentation is in src/documentation/xdocs. This is the raw content that Forrest will transform into HTML and PDF. The "seed" target we ran for setup copied in a whole bunch of sample files that you can browse through. We can delete most of them, leaving index.xml, site.xml, and tabs.xml.

The site.xml and tabs.xml files control navigation. I modified the ones in the downloadable source accompanying the article (see "Resources" below) to have one tab with links to this article and to a new XML file, example.xml, which demos the plugin we'll develop later. Note that there's no DTD: you can have arbitrary XML under <site> that reflects your navigational structure.

In this case we're creating one block, about, that we'll name in tabs.xml to use on our single tab. This free-form XML structure creates a hierarchical table of contents for the site. Nesting indicates different menu levels, and whole blocks can be associated with a tab, as previously mentioned. Drilling down, the article and example elements link to the two files I added to what forrest seed created for us. Referencing these elsewhere will be as simple as <link href="site:article"> and <link href="site:example">; even if filenames change, your navigational links can stay the same.

<about label="About">
    <index label="Index" href="index.html"
        description="Intro to Forrest"/>
    <article label="The Article" href="article.html"
        description="Article on Forrest"/>
    <example label="Code example: API docs"
        href="example.html"
        description="Extension for Forrest"/>
    <changes label="Changes" href="changes.html"
        description="History of Changes"/>
    <todo label="Todo" href="todo.html"
        description="Todo List"/>
</about>

Take a look at example.xml to see the XML structure for content. It looks a lot like HTML, but with all of the style-related code taken out. You can use elements such as <warning> and <section> to structure the content without declaring how it will be displayed. If you know HTML, you can learn Forrest's subset in an hour; it's very natural.

Re-Skinning the Documentation

The default skin is quite Apache-specific. The favicon.ico is an Apache feather icon, the color scheme matches Apache's web site, etc. I prefer the Krysalis skin, and with Forrest the change is easy. Just open up forrest.properties and uncomment project.skin=krysalis-site, then re-run Forrest. You can see an example of the Krysalis look for Forrest documentation here.

Getting Down to Code: Adding the api Link Type

Forrest supports two link types: site and ext. Site links, such as site:about, get mapped to XML elements in site.xml, under <site>. External links, such as ext:forrest, get mapped to XML elements in the same file, under <external-refs>. This lets you change your site structure and sites you link to outside without altering your documentation content -- another example of our theme of separating view from documentation.

One link any Java developer would want is to Javadocs. Given a class name, it would be nice to have Forrest generate an HTML link to its Javadoc; e.g., api:foo.Bar might become apidocs/foo/Bar.html when translated. We're going to add an extension that will do just this, using the same mechanism that makes site and ext work.

Cocoon uses the concept of an "input module" in lots of different places. It's essentially a component representing a universe of name-value pairs. Different implementations can interpret different kinds of names. At the top level, we want a name such as example/foo.Bar to translate to a path to a local Javadoc. To get at this, we need an additional level of indirection, to translate example to a base path, to which our root module can then append a path to an actual Javadoc HTML file. This lets us have more than one set of Javadocs, and make their locations configurable. We might have one set of Javadocs called example1 and another called example2, for instance. In the code and the XML, I'll refer to this name as a set ID.

This is where the reuse and nesting of InputModules starts to come in handy. Cocoon already has a module that can read from a file and interpret the incoming names as XPaths, and return the value of an XPath as the value. We'll use this to read the site.xml file that Forrest uses to map virtual to actual links. Another chainable module knows how to add a prefix and a suffix to the name requested. We use this to prefix our XPath with /site/apidocs and append /@base. What goes in the middle is the name requested by our custom module, ApidocInputModule. We'll pass in the set ID here, and get back an XPath that looks like /site/apidocs/example/@base. With a little string manipulation, we can convert the class name to a path to HTML, append it to the base we got back, and voilà: a full path to a Javadoc given a class name.

That's a good deal of hand-waving, but because we're taking advantage of Cocoon's rich set of features to do this with just a few lines of code, I wanted to go over what it does first. First we want to implement InputModule. Because we'll be chaining together other InputModules, we'll subclass a convenience base class for this purpose, AbstractMetaModule. Our primary method is getAttribute(), which maps a name to a value.

The first order of business is to configure the module and separate out the Javadoc set name from the class name.

public Object getAttribute(String name,
        Configuration configuration,
        Map objectModel)
    throws ConfigurationException {

    // meta module doesn't set up the child
    // InputModule until asked
    lazy_initialize();

    // Cocoon lets you supply both a default
    // configuration and a runtime configuration;
    // we need to pick up the latter
    Configuration inputConfig = null;
    String inputName = null;
    if (modeConf != null) {
        inputName = modeConf.
            getChild("input-module").
            getAttribute("name", null);
        if (inputName != null) {
            inputConfig = modeConf.
                getChild("input-module");
        }
    }

    // the first part of the name is the set;
    // if none, it becomes "default"--the rest
    // is the class name and optionally the
    // method name
    String setName = null;
    int ndxSlash = name.lastIndexOf('/');
    if (ndxSlash == -1) {
        setName = "default";
    } else {
        setName = name.substring(0, ndxSlash);
    }

    ....
}

First, a note on the extra parameters to the configure() method, which we'll (mostly) ignore for this plugin. Configuration provides access to the runtime parameters passed to the module, and the objectModel parameter provides access to the Cocoon environment. I said "mostly" because in an earlier draft of the code, I found the plugin wasn't working because it hadn't yet been configured and initialized. We'll do this by calling the lazy init method, as shown, making sure the runtime overrides for configuration get used (if any; Cocoon lets us configure at different levels), and finally overriding configure() to read that nested <input-module> that I initially neglected:

public void configure(Configuration config)
 throws ConfigurationException {
    super.configure(config);
    this.inputConf = config.
        getChild("input-module");
    this.defaultInput = this.inputConf.
        getAttribute("name", this.defaultInput);
}

Pages: 1, 2

Next Pagearrow