Saturday, October 20, 2007

BIRT: Parameterized Page Break Interval

A useful trick to know with BIRT is how to control the pagination for report output. It would be nice if you could present the user with a parameter to control the number of rows displayed per page and let them decide. While there is the page_break_interval property in a table, it is not easily exposable. Thanks to Jason Weathersbys of BirtWorlds help, I have a simply answer on how to create a parameter and assign the page_break_interval the value I need.

What I did was create a user parameter called NewParameter. I know, clever name, but this was just at test scenario. I have a table called counTable that will output the results of a dataset. Then, I used the following script in the tables onPrepare event handler (although this could be done in the reports Initialize event handler also):

tableContext = reportContext.getReportRunnable().designHandle.getDesignHandle().findElement("countTable");

tableContext.setPageBreakInterval(params["NewParameter"]);


That’s it. Now, when the report is displayed, a parameter will prompt the user to enter the number of rows to break each page on. This is useful when used with the Progressive Viewing feature to allow the user to really control pagination when embedding BIRT into an application. The completed report is below in XML.

<?xml version="1.0" encoding="UTF-8"?>
<report xmlns="http://www.eclipse.org/birt/2005/design" version="3.2.14" id="1">
<property name="author">John Ward</property>
<property name="createdBy">Eclipse BIRT Designer Version 2.2.0.v20070620 Build &lt;2.2.0.v20070626-1003></property>
<property name="units">in</property>
<text-property name="title">Page Break Interval</text-property>
<property name="comments">Copyright (c) 2007 &lt;&lt;Your Company Name here>></property>
<html-property name="description">A test report that will generate a data set with 6000 numbers, then assign the tables page break interval using a parameter.</html-property>
<text-property name="displayName">Blank Report</text-property>
<property name="iconFile">/templates/blank_report.gif</property>
<parameters>
<scalar-parameter name="NewParameter" id="6">
<property name="valueType">static</property>
<property name="dataType">string</property>
<text-property name="promptText">Page Break Interval</text-property>
<property name="controlType">text-box</property>
<property name="defaultValue">5</property>
<structure name="format">
<property name="category">Unformatted</property>
</structure>
</scalar-parameter>
</parameters>
<data-sources>
<script-data-source name="Data Source" id="23"/>
</data-sources>
<data-sets>
<script-data-set name="Data Set" id="24">
<list-property name="resultSetHints">
<structure>
<property name="position">0</property>
<property name="name">count</property>
<property name="dataType">any</property>
</structure>
</list-property>
<list-property name="columnHints">
<structure>
<property name="columnName">count</property>
</structure>
</list-property>
<structure name="cachedMetaData">
<list-property name="resultSet">
<structure>
<property name="position">1</property>
<property name="name">count</property>
<property name="dataType">any</property>
</structure>
</list-property>
</structure>
<property name="dataSource">Data Source</property>
<method name="open"><![CDATA[x = 0;]]></method>
<method name="fetch"><![CDATA[if (x < 6000)
{
x++;
row["count"] = x;

for (y = 0; y < 6000; y++);

return true;
}

return false;]]></method>
</script-data-set>
</data-sets>
<page-setup>
<simple-master-page name="Simple MasterPage" id="2">
<page-footer>
<text id="3">
<property name="contentType">html</property>
<text-property name="content"><![CDATA[<value-of>new Date()</value-of>]]></text-property>
</text>
</page-footer>
</simple-master-page>
</page-setup>
<body>
<table name="countTable" id="27">
<property name="width">100%</property>
<property name="dataSet">Data Set</property>
<list-property name="boundDataColumns">
<structure>
<property name="name">count</property>
<expression name="expression">dataSetRow["count"]</expression>
<property name="dataType">any</property>
</structure>
</list-property>
<method name="onPrepare"><![CDATA[tableContext = reportContext.getReportRunnable().designHandle.getDesignHandle().findElement("countTable");

tableContext.setPageBreakInterval(params["NewParameter"]);]]></method>
<column id="36"/>
<header>
<row id="28">
<cell id="29">
<label id="30">
<text-property name="text">count</text-property>
</label>
</cell>
</row>
</header>
<detail>
<row id="31">
<cell id="32">
<data id="33">
<property name="resultSetColumn">count</property>
</data>
</cell>
</row>
</detail>
<footer>
<row id="34">
<cell id="35"/>
</row>
</footer>
</table>
</body>
</report>

Sunday, October 07, 2007

BIRT: Progressive Viewing during Render

A coworker and I recently engaged is a discussion about the requirements for a client of ours. Although this ultimately wasn’t the solutions we ended up going with, one of the possible options for this client was to do what is called Progressive Viewing in BIRT. Progressive Viewing is the ability for BIRT to view a particular page out of a report document while the Report Document itself is still rendering. If you have ever used the commercial versions of Actuate IServer or ERD Pro, this is familiar. Page 1 gets displayed, while pages 2 – 10 are still being rendered, then when pages 2 – 10 are renders, the next range of pages gets rendered, and the user can view what is ready to view. Since I wanted to know how to do this for an unrelated project, I decided to research how this is done for future reference.

Surprisingly, this was much easier to implement than I would have thought, with BIRT defining an Interface that acts as a Callback when pages are ready. The way it works is for each page event, a callback method is called with the page number, a checkpoint status of either true or false, which means all pages before this page are ready for viewing, and a checkpoint has been reached, and a reference to the Report Document being rendered so you do not need to create a separate Report Document interface. In the OnPage method, you define what you want to do with it. In the case of an interactive viewer, you would simply notify the parent process that the next batch of pages are ready for the user to navigate forward. In the following example, I am rendering out the “Checkpoint” pages when they are ready, and informating the user via the standard console that the next page range is ready. The following bit is a JUnit Test I used for testing the rendering of a custom emitter I was using for a client, slightly modified to do Progressive Viewing rendering. In addition, it also demonstrates doing a Run then Render task in BIRT to allow for pagination.



package test;

import static org.junit.Assert.fail;

import java.util.logging.Level;

import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.EngineException;
import org.eclipse.birt.report.engine.api.IPageHandler;
import org.eclipse.birt.report.engine.api.IRenderTask;
import org.eclipse.birt.report.engine.api.IReportDocument;
import org.eclipse.birt.report.engine.api.IReportDocumentInfo;
import org.eclipse.birt.report.engine.api.IReportEngine;
import org.eclipse.birt.report.engine.api.IReportEngineFactory;
import org.eclipse.birt.report.engine.api.IReportRunnable;
import org.eclipse.birt.report.engine.api.IRunTask;
import org.eclipse.birt.report.engine.api.RenderOption;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class TestXMLEmitter {
private String BIRT_HOME = "C:/BIRT_RUNTIME_2_2/birt-runtime-2_2_0/ReportEngine/";
private String REPORT_DESIGN_FILE = "C:/Workbench/pageBreakInterval.rptdesign";
private String OUTPUT_FILE_LOCATION_RENDER = "C:/temp/myReportOutputRender.xml";
private String OUTPUT_FILE_LOCATION_RUN_RENDER = "C:/temp/myReportOutputRunRender.xml";
private IReportEngine engine;

@Before
public void setUp() throws Exception {
EngineConfig config = new EngineConfig();
config.setBIRTHome(BIRT_HOME);
config.setLogConfig("C:/TEMP/", Level.ALL);

try {
Platform.startup( config );
IReportEngineFactory factory = (IReportEngineFactory) Platform.createFactoryObject( IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
engine = factory.createReportEngine(config);
engine.changeLogLevel(Level.ALL);
} catch (RuntimeException e) {
e.printStackTrace();
}
}

@After
public void tearDown()
{
engine.shutdown();
Platform.shutdown();
}

/*@Test
//Removed, for our purposes, we do not need to do a run and render task
public void testReportRun() throws Exception {
try {
//once the engine is started, create the report task
IReportRunnable design = engine.openReportDesign(REPORT_DESIGN_FILE);

IRunAndRenderTask task = engine.createRunAndRenderTask(design);

//RenderOption renderOption = new RenderOption();
RenderOption renderOption = new RenderOption();

renderOption.setOutputFormat("MyXMLEmitter");
renderOption.setOutputFileName(this.OUTPUT_FILE_LOCATION_RUN_RENDER);

renderOption.setOutputStream(System.out);
task.setRenderOption(renderOption);

task.setParameterValue("NewParameter", 5);

task.run();

task.close();

} catch (RuntimeException e) {
engine.getLogger().log(Level.SEVERE, e.getMessage(), e);
fail(e.getMessage());
}
} */

@Test
public void testReportRender() throws Exception {
try {
//create the report task and open the report design file
IReportRunnable design = engine.openReportDesign(REPORT_DESIGN_FILE);
IRunTask runTask = engine.createRunTask(design);

//For our test report, we have a parameter called NewParameter, that
//is actually setting the pageBreakInterval property. We need to set this
//before running our report
runTask.setParameterValue("NewParameter", 5);

//Define the callback handler for new page events
runTask.setPageHandler(new MyPageHandler());

//run and close the report run task
runTask.run("C:/temp/tempDoc.rptDocument");
runTask.close();

//Inform the user running is complete
System.out.println("Done running");
} catch (RuntimeException e) {
engine.getLogger().log(Level.SEVERE, e.getMessage(), e);
fail(e.getMessage());
}
}

/**
* Class MyPageHandler
*
* This class will handle new page events for the Report Run Task
*/
public class MyPageHandler implements IPageHandler {

//Define local variables for the callback class
private String OUTPUT_FILE_LOCATION_RUN_RENDER = "C:/temp/myReportOutputRunRender";
private int lastCheckpoint = 0;

/**
* void onPage
*
* @param pageNumber - the page number that is currently be called for event
* @param readyForViewing - is this event a Check POint event
* @param reportDocument - instance to the report document
*/
public void onPage(int pageNumber, boolean readyForViewing, IReportDocumentInfo reportDocument) {
//we only want to do something if this is a checkpoint event
if (readyForViewing)
{
//Just let the user know that the next page ranges are ready, then set the last check point to the
//current page
System.out.println("Pages " + lastCheckpoint + " through " + pageNumber + " are ready for viewing");
lastCheckpoint = pageNumber;

try {
//open the report document then create the render task from it
IReportDocument iReportDocument = reportDocument.openReportDocument();
IRenderTask task = engine.createRenderTask(iReportDocument);

//Set Render context to handle url and image locataions
RenderOption renderOption = new RenderOption();

//set the output file format and the emitter output in the render options
renderOption.setOutputFileName(OUTPUT_FILE_LOCATION_RENDER + pageNumber + ".xml");
renderOption.setOutputFormat("MyXMLEmitter");
task.setRenderOption(renderOption);

//Render Page and close the render task
task.setPageNumber(pageNumber);
task.render();
task.close();

System.out.println("Page " + pageNumber + " is ready for viewing");
} catch (EngineException e) {
e.printStackTrace();
} catch (BirtException e) {
e.printStackTrace();
}
}
}
}
}