Skip to main content
Blog

Paginating using Pojo BIRT Runtime

By 19 juli 2013januari 30th, 2017No Comments

In a previous blog post I discussed how to use the Pojo BIRT Runtime to render an HTML report.

If you are embedding BIRT reports into a website and generating them on the fly you will probably want pagination as well. As discussed earlier, the BIRT Viewer can take care of this for you but if you need tighter control you can do it using the Pojo BIRT Runtime.

To have pagination I need to split the RunAndRenderTask we used before into a separate RunTask and a RenderTask. The RunTask gathers the data and the RenderTask renders it in the specified format.
First I will generate all the data for all pages and then render only the specific page. This allows me to know how many pages there are, so I can show a paginator and embed it (in for example a JSF Data table). Doing so is left for an upcoming blog post though ;). I can also use this to allow a user to download the full report in a different format after previewing it as HTML on screen.

Since the RunTask will gather the data which will later be consumed by the RenderTask, I need some place to store the data:
it is a waste of resources to rerun the RunTask for every page.
I could store the data in memory (see the reference below for an example of MemoryArchive), but since I expect multiple users to run reports concurrently and read through multiple pages, I expect it will take up too much memory. So, I’ll resort to temporary files which I’ll need to clean up later, though doing is is left out in this post.

Since I want to hide the actual implementation from the end user, I’ll use a ReportDataHandle class which represents the output of a RunTask:

public class ReportDataHandle {
    private final String fileName;
    private final String reportResourceName;

    ReportDataHandle(String fileName, String reportResourceName) {
        this.reportResourceName = reportResourceName;
        this.fileName = fileName;
    }

    String getFileName() {
        return fileName;
    }

    String getReportResourceName() {
        return reportResourceName;
    }

    public boolean isValid() {
        return new File(fileName).exists();
    }

    public void invalidate() {
        new File(fileName).delete();
    }
}

We can now create the handle using something like this:

    public ReportDataHandle createReportDataHandle(final String reportResourceName,
            final Map<String, Object> reportParameters) throws EngineException, SQLException, IOException {
        IReportEngineFactory factory =
                (IReportEngineFactory) Platform
                        .createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
        IReportEngine engine = factory.createReportEngine(CONFIG);
        IReportRunnable design =
                engine.openReportDesign(ReportEngineFactory.class.getResourceAsStream(reportResourceName));

        IRunTask task = engine.createRunTask(design);
        task.setParameterValues(reportParameters);

        File tempFile = File.createTempFile("birt-temp-doc-archive-", ".tempReportDocument");
        IDocArchiveWriter writer = new FileArchiveWriter(tempFile.getAbsolutePath());
        task.run(writer);
        return new ReportDataHandle(tempFile.getAbsolutePath(), reportResourceName);
    }

Then, we can use that handle to render a page. After a first time render, we know how many pages there are in total. To do that, we leave the page number empty (null). This will cause the render task to render the first page but also calculate how many pages there are. For successive pages, we simply give a page number.

public long render(final ReportDataHandle reportDataHandle, final ReportContentType contentType, OutputStream outs,
            Long pageNr) {

            IReportEngineFactory factory =
                    (IReportEngineFactory) Platform
                            .createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
            IReportEngine engine = factory.createReportEngine(CONFIG);

            IDocArchiveReader reader = new FileArchiveReader(reportDataHandle.getFileName());
            IReportDocument reportDocument =
                    engine.openReportDocument(reportDataHandle.getReportResourceName(), reader, new HashMap());
            IRenderTask task = engine.createRenderTask(reportDocument);
            task.setRenderOption(getRenderOptions(outs));
            if (pageNr != null) {
                task.setPageNumber(pageNr);
            }
            task.render();
            return task.getPageCount();
    }

If you want to use this in a webpage, you can for example make the ReportDataHandle serializable and store it in the HttpSession. I would advice to create some sort of cache where you use the report design and its parameters as a key and the handle as a value. This way, you can control the number of handles and create eviction strategies for them, or even share or distribute them on a cluster.

Some references: