View Source

Not really satisfied with the available reporting solution for Java I decided to brew my own solution. It consists of the following layers:

# Domain objects (populated using Hibernate)
# A Velocity template for generating the xml
# An XSL template for generating the XSL-FO
# The Apache FOP renderer for generating the PDF

The domain objects are java beans with getter methods for all properties. Velocity can interpret the standard ${} markup . All variable placeholders are replaced with bean property values. The variables can contain members of members. This is the Velocity template:

{code:xml}
<?xml version="1.0" encoding="UTF-8"?>
<report>
<!-- Template for rendering timesheet xml report -->
<title>${timeSheet.description}</title>
<customer>${timeSheet.customer.name}</customer>
employee>${timeSheet.resource.userFullName}</employee>
<activities>
#foreach($activity in ${timeSheet.activities})
<activity>
<project>${activity.assignment.project.name}</project>
<date>${activity.activityDate}</date>
<formattedDate>${dateFormatter.formatDate($activity.activityDate)}</formattedDate>
<hours>${activity.hours}</hours>
<issue>${activity.issueId}</issue>
<description>${activity.description}</description>
<percentage>${activity.billablePercentage}</percentage>
<billablehours>${activity.billableHours}</billablehours>
</activity>
#end
</activities>
</report>
{code}

The java code triggering Velocity looks like this (exception handling omitted):
{code}
Velocity.init();
VelocityContext velocityContext = new VelocityContext();
Template template = Velocity
.getTemplate("WEB-INF/templates/timesheet.vm");
StringWriter stringWriter = new StringWriter();
velocityContext.put("timeSheet", dataSource);
velocityContext.put("dateFormatter", new Dates());
template.merge(velocityContext, stringWriter);
{code}

Here is a snippet of the XSL file for transforming this xml report to the XSL-FO:
{code:xml}
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:fo="http://www.w3.org/1999/XSL/Format">

<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:decimal-format decimal-separator="," grouping-separator="." name="nl"/>

<xsl:template match="/">
<fo:root>
<fo:layout-master-set>
<fo:simple-page-master margin="1.5cm" page-width="21cm" page-height="29.7cm" master-name="first">
<fo:region-before>

</fo:region-before>
<fo:region-body/>
</fo:simple-page-master>
</fo:layout-master-set>
<fo:page-sequence master-reference="first">
<fo:flow flow-name="xsl-region-body" font-family="Helvetica" font-size="10pt">

<fo:block>
<fo:external-graphic src="http://www.boplicity.nl/images/weblogo.png"/>
</fo:block>

<fo:block font-weight="bold" font-size="11pt" padding-before="1cm" padding-after="0.2cm">
<xsl:value-of select="/report/customer"/>
</fo:block>
<fo:block font-weight="bold" font-size="11pt" padding-before="0.2cm" padding-after="0.2cm">
<xsl:value-of select="/report/title"/>
</fo:block>

<fo:block padding-before="0.6cm" padding-after="0.2cm">
<xsl:text>Medewerker: </xsl:text><xsl:value-of select="/report/employee"/>
</fo:block>

<xsl:apply-templates select="/report/activities"/>
</fo:flow>
</fo:page-sequence>
</fo:root>
</xsl:template>
{code}

Next the the xml is transformed to XSL-FO and rendered by FOP. The resulting byte array can either be piped to a file or sent to the browser.
{code}
private byte[] renderTimeSheet(Integer id) {
String xml = timeSheetDao.findByIdAsXml(id);

// Transform to fo and subsequently pdf
Driver driver = new Driver();
driver.setRenderer(Driver.RENDER_PDF);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
driver.setOutputStream(outputStream);

try {
// Setup JAXP using identity transformer
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer = factory.newTransformer(new StreamSource(
getClass().getResourceAsStream(
"/com/bop/metronome/reports/timesheet.xsl")));

// Setup input stream
Source source = new StreamSource(new StringReader(xml));

// Resulting SAX events (the generated FO) must be piped through to
// FOP
Result result = new SAXResult(driver.getContentHandler());

// Start XSLT transformation and FOP processing
transformer.transform(source, result);
} catch (Exception e) {
log.error(e.toString());
}
return outputStream.toByteArray();
}
{code}

And all of this coded by hand, no graphical tools used whatsoever. The toughest job was coding the FO. There is a very extensive W3C specification available (http://www.w3.org/TR/xsl/), but no comprehensive 'cookbook' manual, nor GUI tools. So every tiny little layout aspect that I wanted to render took a lot of research time.

Another issue is the performance of FOP. It is quite slow. There is a version coming up however, promising to be much faster: http://xmlgraphics.apache.org/fop/0.91/index.html.