Table of Contents
In the first instance, make sure that extending the tool is really what you need to do. The standard setup is very flexible and you can change the look and feel of the HTML, generate a completely different output language and manipulate graphs to a high degree without writing code to extend BeanDoc. Check the previous chapters if this sounds more like what you want to do.
Generally it should be possible to get BeanDoc to do whatever you
need by implementing either
org.springframework.beandoc.output.Decorator or
org.springframework.beandoc.output.Transformer or
possibly both. Built in implemenations of these two interfaces do most of
the work of generating documentation from the input files. The rest is
handled by the ContextProcessor
implementation(s).
This chapter will explore by way of a simple example the steps involved in adding the required files to produce additional output from that already produced by the tool. If you're genuinely of need of this stuff, the API documentation is going to be your most important resource.
Our trivial example will create a Transformer
that produces a CSV file (Comma Separated Values) which could be loaded
into a spreadsheet or otherwise parsed by text handling tools. All of the
information that we might need in such a file is probably already
available in standard DOM trees after decoration by the
DefaultContextProcessor, so in a slightly contrived
manner we'll assume that we also need a sequential ID for each bean in
order to make it necessary to implement a custom
Decorator. In fact this could be done with an XSL
Transformer implementation, but I'm not going to use
XSL for our Transformer example.
Decorators, Transformers and
DocumentCompilers use JDOM API's for XML processing.
A Decorator has the ability to markup or decorate the XML elements in the DOM tree after the ContextProcessor has created the in-memory representation and performed some remedial decoration of its own. Decoration is incremental and decorators should be careful not to overwrite each other's attributes or element values. To ensure this is the case in our example, we'll use an attribute prefix of "beandocCSV" for all our attribute names.
It's worth ALWAYS specifying attribute names that start with "beandoc" as some of the standard transformations ignore such attributes when actually documenting the context.
Let's start by extending the included SimpleDecorator class from the
BeanDoc source. This class implements the required
decorate() method for us and delegates to a
decorateElement() method which gives us access to every
individual element in each context file in turn.
The Decorator interface is simple with a single
method to implement;
public interface Decorator {
public void decorate(Document[] contextDocuments);
}The default context processor implementation in BeanDoc will pass in
the array of JDOM Documents
that it has built in order that the implementation might amend values or
add attributes. A convenience implementation of this interface called
SimpleDecorator subsequently iterates each
Element of each Document and calls a
protected decorateElement() method for subclasses to
fill out. A subclass can specify a Filter to restrict
the Element types it is presented with for decoration
This is the pattern we'll follow here;
public class CsvDecorator extends SimpleDecorator {
private static final String ATTRIBUTE_PREFIX = "beandocCSV";
static final String ATTRIBUTE_COUNTER = ATTRIBUTE_PREFIX + "Count";
private int count = 0;
/**
* specify only 'bean' elements as a Filter - we're not interested in
* decorating anything else
*/
public CsvDecorator() {
setFilter(new ElementFilter("bean"));
}
/**
* @see org.springframework.beandoc.output.SimpleDecorator#decorateElement(org.jdom.Element)
*/
protected void decorateElement(Element element) {
element.setAttribute(ATTRIBUTE_COUNTER, String.valueOf(++count));
}
}That's it. There's nothing else for our Decorator
implementation to do - we've added a sequential id to each bean in the
context. All the other information we need for our transformation to the
CSV file is already available.
One more important point to note when writing
Decorators: you can add or change attributes and change
the value of Elements, but any attempt to change the
structure of the DOM by adding, removing or moving
Elements will result in a runtime exception.
Our transformation could use XSLT to generate the CSV but here we'll
do it on the cheap and just use a FileWriter to output our document.
Satisfying the Transformer interface involves
implementing a single method as shown;
public interface Transformer {
public void transform(Document[] contextDocuments, File outputDir);
}Here's our CSV implementation;
public class CsvTransformer implements Transformer {
/**
* create a new CSV file for each context file
*/
public void transform(Document[] contextDocuments, File outputDir) {
for (int i = 0; i < contextDocuments.length; i++) {
Document doc = contextDocuments[i];
File outputFile = new File(
outputDir, doc.getRootElement().getAttributeValue("beandocFileName") + ".csv"
);
try {
FileWriter out = new FileWriter(outputFile);
String csvLine = "";
List beans = doc.getRootElement().getChildren("bean");
for (Iterator iter = beans.iterator(); iter.hasNext();) {
Element bean = (Element) iter.next();
csvLine =
bean.getAttributeValue(CsvDecorator.ATTRIBUTE_COUNTER) + "," +
bean.getAttributeValue("id") + "," +
bean.getAttributeValue("class") + "\n";
out.write(csvLine);
}
out.flush();
out.close();
} catch (IOException e) {
// should be handled properly!
e.printStackTrace();
}
}
}
}The class is instructive only, it clearly doesn't attempt to handle exceptions correctly or ensure that resources are flushed or closed under such cases.
The two classes detailed above are included in the main source tree of BeanDoc and compiled into spring-beandoc.jar. If you do want to try them out, you only need folllow the instructions in the next section.
After decoration and transformation of DOM trees, the default
context processor will then offer any configured
DocumentCompilers the chance to pull the output
resources together. In many cases this won't be necessary as
transformation will produce documents in the required format. In the case
of our simple example, we don't need to implement a new
DocumentCompiler.
Should you need to do so, the operation is trivial involving writing
a class to implement the single interface method in similar fashion to
Decorators and Transformers.
public interface DocumentCompiler {
public void compile(Document[] contextDocuments, File outputDir);
}If you are extending BeanDoc with your own classes such as those
outlined in this chapter, you will need to instruct the ContextProcessor
implementation to use them. BeanDoc uses a Spring bean factory to
configure itself and the Ant task has an attribute that permits you to
specify a context definition file to override the internal one. This is
what you must do in order to make use of your new
Decorator or Transformer
implementations.
The samples directory contains a context file that includes the CSV
output classes as part of the List of
Decorator and Transformer
implementations supplied to the processor;
<bean id="processor" class="org.springframework.beandoc.DefaultContextProcessor">
<description>
main processor for generating output
</description>
<constructor-arg index="0"><value>${input.files}</value></constructor-arg>
<constructor-arg index="1"><value>${output.dir}</value></constructor-arg>
<property name="decorators">
<list>
<ref local="javadoc"/>
<ref local="html"/>
<ref local="graphs"/>
<ref local="csvDecorator"/>
</list>
</property>
<property name="transformers">
<list>
<ref local="htmlIndexTransformer"/>
<ref local="htmlNavigationTransformer"/>
<ref local="htmlMainTransformer"/>
<ref local="htmlDetailTransformer"/>
<ref local="graphVizTransformer"/>
<ref local="csvTransformer"/>
</list>
</property>
...
</bean>
<bean id="csvDecorator" class="org.springframework.beandoc.output.CsvDecorator"/>
<bean id="csvTransformer" class="org.springframework.beandoc.output.CsvTransformer"/>
...It may appear more natural to nest the "csv" beans as anonymous
inner beans instead of supplying <ref> tags
for them in the <list>s. Remember though that
unless they are top level beans with a referenceable id, you will be
unable to post-process them by setting properties in
beandoc.properties.
Your <beandoc/> task in Ant's buildfile
will need to be told where your alternative context definition file is,
for example;
<beandoc
inputFiles="${basedir}/petclinic/*.xml"
outputDir="/some/other/outputdir/html/"
beandocContext="${basedir}/alternative-beandoc.xml"
/>See ??? for information on running BeanDoc from an Ant script.