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();
}
}
}
}
}

9 comments:

Anonymous said...

Hi John,
I am faced with the same problem as what you have mentioned. I have deployed my App on Tomcat 5.5 and I want to implement Progressive Viewing for my BIRT Report. (MY app takes about 6-7 minutes to generate the whole report!!!) I am still not sure as to how to go about this (even after reading this blog).
It would be greatly appreciated if you could tell me the steps on how to go about this...?
A response at the earliest will cost me a hug :-)

p.s - the BIRT Runtime version is use is currently 2.3.2.

Anonymous said...

Forgot to add that I am new to BIRT and I am displaying my report through a JSP as of now.
:-)

John Ward said...

Sorry for the delay. I've been in a funk when it comes to the blog over the past few months. Workload and prepping for EclipseCon has got me tied down.

I just went back and looked at it, and the example on this page should work for 2.3.2.

How you do this in your app is up to you. In similar web apps, what I've done is use GWT and had a callback that handled notifying the client app that a page was ready for viewing.

So, in the case of this example (which was used in a JUnit Test) you do the same thing you would do in a BIRT Run Then Render Task (which is different from a Run And Render Task). What happens is the onPage method in the MyPageHandler gets called every time a page gets called each time a page is ready for viewing. In the OnPage event, the ready page is rendered.

So, what I did in my web app is each time a page is ready, a listener status gets set with the max available pages. The client calls the listener and gets a response with the max number of pages. Then, when the client wants to view a page, it calls with the page number to view, the renderer will render that page, and return it. This all happens asynchronously, so the report can be running in the background, updating the listener, and the renderer works independently.

Thats just a high level idea of how to do it.

Hope that helps,

John

Anonymous said...

In fact it did ! Thanks John!!

Rosh said...

Hi John,
Your example was really good. But I wanted to know if there is another way to implement Progressive Viewing without implementing Event Handlers in Java Classes?
Like probably some setting in the Report Design itself? Is there some provision?
- Roshna

John Ward said...

No. Progressive viewing is part of the render process, and you cannot control the render process in script.

Anonymous said...

Hi,
Thanks a lot for your blog. But I have a small issue. We have a huge data coming in and want to implement Paging. So, this approach will work.
I know like to know your approach on how to handle huge amounts of data with pagination and the present approach described.

Again, thanks a lot for explaining us this .

Thanks and Regards,
Chaitanya

John Ward said...

I'm not sure what you mean. Progressive viewing is meant to allow users to view data as its rendering in cases where there is large amounts of data. If a user is generating a several hundred page report, its better to have them able to view pages as they are ready than to have to wait until rendering is complete.

Anonymous said...

Hi John,

Thanks for your blog. I am new to BIRT. Currently in our project we use BIRT 2.3.2 We Viewer to display our reports. We are running into performance issue. If you can detail the steps to perform the progressive rendering for web viewer it would be great.

Also any suggestions to improve the report rendering process would be helpful.

Thanks a lot in advance :).

Regards,
Chris.