... 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}
|