Calling BIRT from PHP
Introduction
BIRT is a top-level, open-source, Eclipse project that provides business intelligence and reporting tools. Currently, BIRT is best known as a report-creation tool that supports multiple data sources and produces many different forms of output, including HTML, paginated HTML, PDF, Word, Excel, PPT, and postscript formats.
BIRT provides many features for report creation, including dynamic crosstabs, charts, grouping, filtering, and cascaded dynamic parameters. For deployment, BIRT offers the BIRT Viewer, which is an AJAX-based, J2EE component that features a JSP Tag library, HTML-paging, exporting to supported formats, including data export, table of contents, and pagination controls. In addition, the BIRT engine can be deployed in a Java environment
The BIRT Viewer and the BIRT engine work well in a Java/J2EE environment, but what if your web applications are built using PHP? In order to call BIRT from PHP, a PHP-to-Java bridge is necessary. Zend offers a commercial version of a supported PHP/Java bridge, including PHP BIRT wrapper classes, and works very well. There are also several open-source variants of PHP/Java bridges available.
In this article, we will illustrate integrating BIRT and PHP using the popular PHP/Java Bridge open source project located at http://php-java-bridge.sourceforge.net/pjb/. Over the last several weeks, the BIRT and PHP/Java bridge development teams worked together to integrate BIRT into the JavaBridge WAR sample file and this is available with the 5.4.3.3 version of the PHP/Java Bridge and can be downloaded athttp://sourceforge.net/project/showfiles.php?group_id=117793. The BIRT integration is located in the php-java-bridge_5.4.3.3_documentation.zip download. Currently the version of BIRT that is integrated is BIRT 2.2.2.
- The JavBridgeTemplate5433.war download is a template for creating new WAR files that use the PHP/Java Bridge. The template was used to create a version of the WAR that contains BIRT 2.3.1 which can be found on Birt Exchange at http://www.birt-exchange.com/devshare/deploying-birt-reports/743-calling-birt-from-php/.
Overview of Integration
The PHP/Java Bridge offers a lot of installation options. In this article, we are going to focus on the options that allow you to call Java objects from existing PHP applications. When calling Java code from PHP applications, your Java code can be executing in a standalone JVM or running as part of a J2EE web application. This article details calling Java code from within your PHP applications, specifically calling the BIRT engine to run reports, when the PHP/Java Bridge is deployed to a J2EE application server.
When calling the PHP/Java Bridge in this way, the bridge is deployed to a J2EE application server and communicates with PHP using the VM Bridge Network Protocol (XML Based Protocol), which is described athttp://php-java-bridge.sourceforge.net/pjb/PROTOCOL.TXT. To access specific Java Objects in this scenario, these Java classes can be added to the JavaBrige applications lib directory and deployed to the J2EE application server as normal.
Figure 1 – PHP/Java Bridge using VM Bridge Network Protocol
BIRT uses OSGi plugins to implement its functionality, so the JavaBridge WAR file has been modified to include the BIRT Engine libraries and OSGi plugins.
Figure 2 – Modified JavaBridge WAR
In addition, a Java class was created to facilitate managing the BIRT engine within this environment. The class is named BirtEngine and is available in the birtEngine.jar file located in the JavaBridge WAR file.
The following downloads were used for the development of the samples associated with this article:
Installing the PHP/Java Bridge into Tomcat
The installation process is very simple and is described in the PHP/Java Bridge download. The main step is to copy the JavaBridge.war file to the J2EE containers deployment directory. When using Tomcat, this directory is located at TomcatInstallDirectory/webapps.
Before copying the JavaBrige WAR file to the Tomcat webapps directory, you should set the MaxPermSize JVM setting. If you have Tomcat running as a service, switch to the TomcatInstallDirectory/bin directory and run the tomcat5w.exe. The Tomcat service should be running to configure the JVM parameter. Switch to the Java tab and add the following to the Java Options list:
-XX:MaxPermSize=256m
If you are not running Tomcat as a service, you can modify the Catalina.bat file located in the TomcatInstallDirectory/bin directory. Add the MaxPermSize setting to the to the JAVA_OPTS environment variable:
set JAVA_OPTS=%JAVA_OPTS% -XX:MaxPermSize=256m
Copy the BIRTBridge.war file to the Tomcat install/webapps directory and startup Tomcat.
Testing the Installation
In this article, we are using Apache 2.2 as our web server, so we create a directory under the Apache htdocs directory called BirtBridgeTest. When the JavaBrige war is deployed, Tomcat will create a directory called JavaBridge, under the TomcatInstallDirectory/webapps directory. Next, copy the TomcatInstallDirectory/webapps /JavaBridge/java directory from this new JavaBridge directory structure to the BirtBridgeTest directory. This copy is only necessary if your PHP server prevents remote access for include and require statements. We can now create a simple PHP page to verify that we can access Java components.
getProperties(); ?>
Some PHP servers prevent the require statement from accessing remote sites. If the above code fails, you can either set allow_url_include=On in the php.ini file or change the code to the following.
getProperties(); ?>
If the code runs successfully, a dump of the JVM system properties should be displayed.
What can you do with the BIRT Report Engine
The bulk of this article describes various ways to call the BIRT Report Engine to run and render reports through PHP. Before getting into the details, an overview of the BIRT engine is needed.
The BIRT report engine executes and renders BIRT report designs. These designs are XML-based and are created using the BIRT Report Designer. The engine can emit output in the currently supported output formats: HTML, DOC, XLS, PPT, PDF, and Postscript. The engine uses tasks to implement its functionality. For example, to run and render a report to one the output formats, the engine provides a RunAndRenderTask. Starting up the BIRT engine is resource intensive; therefore using only one instance for an application is the recommended usage scenario. Application developers can wrap the engine startup in a singleton object then use the same engine to create new task for every thread that wishes to use the engine. Fortunately, the PHP/Java Bridge now provides hooks that allow developers to construct code that is called when the bridge is created and when it is shutdown. In the PHP examples that follow in later sections of this article, this process will be further detailed. The BIRT report engine provides tasks for the following operations.
Table 1-1 BIRT report engine tasks
Task Name
|
Task Description
|
RunAndRenderTask
|
This Task uses one process to execute a BIRT report design and renders in one of the supported formats.
|
RunTask
|
This Task uses the BIRT Engine to consume a BIRT Report design and produces an intermediate binary file, containing the report design and data, that can be rendered at a later time. The document has an rptdocument extension.
|
RenderTask
|
This Task reads the contents of an rptdocument and renders specific pages to one of the supported output formats.
|
GetParameterDefinitionTask
|
This Task is used to retrieve parameter definitions from a report design. It can also be used to implement cascaded parameter support, within an application
|
DataExtractionTask
|
This Task is used to retrieve report data that is stored in an rptdocument file.
|
Figure 3 – BIRT report engine task
PHP pages that implement the various BIRT Engine Tasks
To call BIRT through PHP using the new JavaBridge.war is pretty straight forward. This section details using the integration to perform some of the most common BIRT report engine API calls through PHP. The following examples were tested using the configuration outlined earlier in this article.
RunAndRenderTask To HTML
The simplest task that the engine can perform is a RunAndRenderTask. This task simply takes a BIRT Report design, processes it, then emits to one the supported output formats. The following example illustrates this calling this task and emits an HTML output. This example is available in the PHP/Java Bridge download.
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $report = $birtReportEngine->openReportDesign("${here}/TopNPercent.rptdesign"); $task = $birtReportEngine->createRunAndRenderTask($report); $task->setParameterValue("Top Count", new java("java.lang.Integer", 6)); $taskOptions = new java("org.eclipse.birt.report.engine.api.HTMLRenderOption"); $outputStream = new java("java.io.ByteArrayOutputStream"); $taskOptions->setOutputStream($outputStream); $taskOptions->setOutputFormat("html"); $ih = new java( "org.eclipse.birt.report.engine.api.HTMLServerImageHandler"); $taskOptions->setImageHandler($ih); $taskOptions->setBaseImageURL($imageURLPrefix . session_id()); $taskOptions->setImageDirectory($here . "/sessionChartImages/" . session_id()); $task->setRenderOption( $taskOptions ); $task->run(); $task->close(); } catch (JavaException $e) { echo $e; //"Error Calling BIRT"; } echo $outputStream; ?>
We are using the session_start statement to create a PHP session. This session is used to store images that are created by the BIRT engine, when running reports that contain images or charts, and emitting to HTML.
$ctx = java_context()->getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook());
These lines of PHP retrieve a handle to the static BirtEngine class, and call a singleton object to get an instance of the BIRT Engine. The BirtEngine class is the only class in this PHP file that is not part of the standard BIRT Report Engine API. The development of this class was part of the integration work that was done. This class was developed to automatically startup and shutdown the BIRT Engine to correspond with the PHP/Java Bridge running status and is available in the birtEngine.jar located in the WEB-INF/lib directory of the JavaBridge.war. The PHP/Java Bridge provides hooks for bridge startup and shutdown events. In this example, we are only using the onShutdown hook to call the getShutdownHook method of the BirtEngine class. This method simply shuts down the BIRT engine and its associated OSGi plugins. We did not need to use the startup hook as we will simply startup the engine on the first request for the BIRT engine resources.
$report = $birtEnginei->openReportDesign("c:/BirtReports/TopNpercent.rptdesign"); $task = $birtEnginei->createRunAndRenderTask($report); $task->setParameterValue("Top Count", new java("java.lang.Integer", 5));
We use the instance of the BIRT engine to open a report design and create a run-and-render task. We also use the setParameterValue method to set the value of one of the report parameters.
When using either the RenderTask or the RunAndRender task, the BIRT engine can output to a file or a ByteArrayOutputStream. In this example, we are outputting the report in HTML format to a ByteArrayOutputStream. The engine is configured for this operation with the following PHP.
$taskOptions = new java("org.eclipse.birt.report.engine.api.HTMLRenderOption"); $outputStream = new java("java.io.ByteArrayOutputStream"); $taskOptions->setOutputStream($outputStream); $taskOptions->setOutputFormat("html");
When the BIRT Engine processes a report that contains an image or chart and the output format is HTML, the output will contain an IMG tag. The source attribute for this tag is determined by what image handler is configured for BIRT. Using an instance of the HTMLServerImageHandler class, we can configure a base URL that is pre-pended to the image name in the source attribute.
$imageURLPrefix = "http://localhost/BIRTBridgeTest/sessionChartImages/"; $taskOptions->setBaseImageURL($imageURLPrefix . session_id()); $taskOptions->setImageDirectory($here . "/sessionChartImages/" . session_id());
Here we are setting the base URL to point to a directory under the BIRTBridgeTest application. This directory is based on the session ID that PHP generates. This directory should allow you to integrate a cron or batch job to clean these images based on what PHP sessions are still active. We also have to set the image path that the BIRT image will write images to.
$taskOptions->setImageDirectory($here . "/sessionChartImages/" . session_id());
Finally we set the options for the task and run the report.
$task->setRenderOption( $taskOptions ); $task->run(); $task->close();
We can then echo the report output with the following PHP statement.
echo $outputStream;
RunAndRenderTask to Other Output Formats
To output this report in other formats using the RunAndRenderTask requires that the render options be changed. For example, to render to PDF we can use the following code.
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $report = $birtReportEngine->openReportDesign("${here}/TopNPercent.rptdesign"); $task = $birtReportEngine->createRunAndRenderTask($report); $taskOptions = new java("org.eclipse.birt.report.engine.api.PDFRenderOption"); $outputStream = new java("java.io.ByteArrayOutputStream"); $taskOptions->setOutputStream($outputStream); $taskOptions->setOutputFormat("pdf"); $task->setRenderOption( $taskOptions ); $task->run(); $task->close(); } catch (JavaException $e) { echo $e; //"Error Calling BIRT"; } //echo $outputStream; echo java_values($outputStream->toByteArray()); ?>
You will notice that we are instantiating a PDFRenderOption object for PDF generation, instead of the HTMLRenderOption object from the previous example. We are also adding the appropriate headers for a PDF document to the output. All other currently supported output formats use a simple RenderOption object. For example, to render to XLS the following code is used:
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $report = $birtReportEngine->openReportDesign("${here}/TopNPercent.rptdesign"); $task = $birtReportEngine->createRunAndRenderTask($report); $taskOptions = new java("org.eclipse.birt.report.engine.api.RenderOption"); $outputStream = new java("java.io.ByteArrayOutputStream"); $taskOptions->setOutputStream($outputStream); $taskOptions->setOutputFormat("xls"); $task->setRenderOption( $taskOptions ); $task->run(); $task->close(); } catch (JavaException $e) { echo $e; //"Error Calling BIRT"; } //echo "test"; //echo $outputStream->toString().trim(); echo java_values($outputStream->toByteArray()); ?>
To emit other formats, the only real difference is the use of the RenderOption object and the header changes. For example to render to DOC format change the headers:
header("Content-type: application/msword"); header("Content-Disposition: inline; filename=downloadedmsdoc.doc");
And change the following setOutputFormat method:
$taskOptions->setOutputFormat("doc");
In all previous examples, the RunAndRenderTask has been used to render to a ByteArrayOutputStream. You can also render to a file as shown in the following example:
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $report = $birtReportEngine->openReportDesign("${here}/TopNPercent.rptdesign"); $task = $birtReportEngine->createRunAndRenderTask($report); $taskOptions = new java("org.eclipse.birt.report.engine.api.RenderOption"); $taskOptions->setOutputFileName($here . "/myreport.xls"); $taskOptions->setOutputFormat("xls"); $task->setRenderOption( $taskOptions ); $task->run(); $task->close(); } catch (JavaException $e) { echo $e; //"Error Calling BIRT"; } echo 'Click Here to download your report'; ?>
In this example, we are simply specifying an output filename instead of a ByteArrayOutputStream.
When rendering to HTML, it may also be desirable to render the contents into an existing HTML page. The BIRT engine provides a render option for this use case. To use it simply set the following option.
$taskOptions->setEmbeddable(true);
This option will remove the head and body tags from the emitted HTML output.
RunTask
The BIRT engine RunTask can be used to create an intermediate binary document called a rptdocument, which contains the report design and data. This task is useful for several reasons. First, it creates a document that can be rendered at a much later time on the same or even a different machine. It does not require the data to be re-queried and allows use of more advanced features of the BIRT engine. These features include the ability to render paginated HTML, export a table of contents, and render specific page ranges. Some of these features will be discussed in the RenderTask section of this article. To create this rptdocument, you can use code similar to the following.
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $report = $birtReportEngine->openReportDesign("${here}/TopNPercent.rptdesign"); $task = $birtReportEngine->createRunTask($report); $task->run($here . "/reportDocuments/" . session_id() . "/mytopnreportdocument.rptdocument"); $task->close(); } catch (JavaException $e) { echo $e; //"Error Calling BIRT"; } echo "Document Created"; ?>
This task is very simple and only requires a report and an output location. In this example, we are writing the report document to a directory that includes the session ID, so it can be cleaned up at a later time.
RenderTask
The BIRT engine RenderTask is used to render report documents created using the render task and offers capabilities, such as setting the page number or range to render. The render options described in previous sections of this article all apply. To render a specific page, the following PHP code can be used:
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $document = $birtReportEngine->openReportDocument($here . "/reportDocuments/" . session_id() . "/mytopnreportdocument.rptdocument"); $task = $birtReportEngine->createRenderTask($document); $taskOptions = new java("org.eclipse.birt.report.engine.api.HTMLRenderOption"); $outputStream = new java("java.io.ByteArrayOutputStream"); $taskOptions->setOutputStream($outputStream); $taskOptions->setOutputFormat("html"); $ih = new java( "org.eclipse.birt.report.engine.api.HTMLServerImageHandler"); $taskOptions->setImageHandler($ih); $taskOptions->setBaseImageURL($imageURLPrefix . session_id()); $taskOptions->setImageDirectory($here . "/sessionChartImages/" . session_id()); $task->setRenderOption( $taskOptions ); $task->render(); $task->close(); } catch (JavaException $e) { echo $e; //"Error Calling BIRT"; } echo $outputStream; ?>
This code is very similar to the code described in the RunAndRenderTask, except that when you create the render task, you pass it the report document instead of the design. The report document can be used to retrieve the table of contents or page count. For example, in the previous code, we use the getPageCount method to determine how many pages are in the document. Finally, before rendering the document, we specify either a page number using the setPageNumber method, a page range using the setPageRange method, or nothing, which returns all pages.
Handling Drill-through Hyperlinks
The BIRT Report designer supports building hyperlinks within the report design. These hyperlinks can be external hyperlinks, internal bookmarks, or drill-through hyperlinks. When using drill-through hyperlinks, the destination is another report.
How the hyperlinks are processed by the report engine depends on a specific object that implements the IHTMLActionHandler interface. By default, BIRT uses the HTMLActionHandler class. When used in the PHP environment, you can write your own instance of the handler or you can use the default one and set the base URL for all drill-through hyperlinks to use. For example, suppose you have a master report that contains orders. For each order, you want to build a hyperlink to drill to a detail report that displays details for the specific order, as shown in the following PHP code:
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $report = $birtReportEngine->openReportDesign("${here}/Master.rptdesign"); $task = $birtReportEngine->createRunAndRenderTask($report); $taskOptions = new java("org.eclipse.birt.report.engine.api.HTMLRenderOption"); $outputStream = new java("java.io.ByteArrayOutputStream"); $taskOptions->setOutputStream($outputStream); $taskOptions->setOutputFormat("html"); $ih = new java( "org.eclipse.birt.report.engine.api.HTMLServerImageHandler"); $taskOptions->setImageHandler($ih); $taskOptions->setBaseImageURL($imageURLPrefix . session_id()); $taskOptions->setImageDirectory($here . "/sessionChartImages/" . session_id()); $taskOptions->setBaseURL( $URLPrefix); $task->setRenderOption( $taskOptions ); $task->run(); $task->close(); } catch (JavaException $e) { echo $e; //"Error Calling BIRT"; } echo $outputStream; ?>
This example opens the Master.rptdesign and executes a RunAndRenderTask to produce the HTML output. One additional render option is used to help process the hyperlinks.
$taskOptions->setBaseURL( $URLPrefix);
Using the setBaseURL simply pre-pends all the drill-through hyperlinks with the specified string. In this case, we are calling another PHP page to process the detail report. The following example shows a typical link created in a report:
10100
Basically, the detail PHP page is called and is passed the report and all of its parameters. The detail PHP page can be coded as follows:
getServletContext(); $birtReportEngine = java("org.eclipse.birt.php.birtengine.BirtEngine")->getBirtEngine($ctx); java_context()->onShutdown(java("org.eclipse.birt.php.birtengine.BirtEngine")->getShutdownHook()); try{ $rpt = $_REQUEST['__report'] ; if( substr(PHP_OS,0,3) == 'WIN' ){ $rpt = substr($rpt, 1); } $report = $birtReportEngine->openReportDesign($rpt); $task = $birtReportEngine->createRunAndRenderTask($report); //Match request parameters with detail report parameters $sh = $report->getDesignHandle()->getParameters(); $parmarraysize = $sh->getCount(); $i = 0; while ($i < $parmarraysize) { $parmhandle = $sh->get($i); $parmname = $parmhandle->getName(); $dt = $parmhandle->getDataType(); $passedinvalue = $_REQUEST[java_values($parmname)]; if (isset($passedinvalue)) { if( strcasecmp(java_values($dt), "integer") == 0 ) { $Parm = new Java("java.lang.Integer", $passedinvalue); $task->setParameterValue($parmname, $Parm); }else if( strcasecmp(java_values($dt), "string") == 0 ) { $Parm = new Java("java.lang.String", $passedinvalue); $task->setParameterValue($parmname, $Parm); } //... add additional data types here } $i++; } $taskOptions = new java("org.eclipse.birt.report.engine.api.HTMLRenderOption"); $outputStream = new java("java.io.ByteArrayOutputStream"); $taskOptions->setOutputStream($outputStream); $taskOptions->setOutputFormat("html"); $ih = new java( "org.eclipse.birt.report.engine.api.HTMLServerImageHandler"); $taskOptions->setImageHandler($ih); $taskOptions->setBaseImageURL($imageURLPrefix . session_id()); $taskOptions->setImageDirectory($here . "/sessionChartImages/" . session_id()); $task->setRenderOption( $taskOptions ); $task->run(); $task->close(); } catch (JavaException $e) { echo $e; } echo $outputStream; ?>
This example is slightly more complex but could be simplified if you choose to hard code parameter names into the PHP page.
//Match request parameters with detail report parameters //Match request parameters with detail report parameters $sh = $report->getDesignHandle()->getParameters(); $parmarraysize = $sh->getCount(); $i = 0; while ($i < $parmarraysize) { $parmhandle = $sh->get($i); $parmname = $parmhandle->getName(); $dt = $parmhandle->getDataType(); $passedinvalue = $_REQUEST[java_values($parmname)]; if (isset($passedinvalue)) { if( strcasecmp(java_values($dt), "integer") == 0 ) { $Parm = new Java("java.lang.Integer", $passedinvalue); $task->setParameterValue($parmname, $Parm); }else if( strcasecmp(java_values($dt), "string") == 0 ) { $Parm = new Java("java.lang.String", $passedinvalue); $task->setParameterValue($parmname, $Parm); } //... add additional data types here } $i++; }
This section of the code uses the report design handle to discover parameter names and types as defined in the report. The request object is then checked to see if any of the parameters are set in the request. If the parameter(s) is/are set in the request, a type conversion is executed and the parameter value is set for the run of the report. The rest of the PHP code is the same as described in earlier sections of this article.
Handling BIRT Libraries
BIRT report designs also support the use of report libraries. These libraries are XML snippets that contain report items, data sources, themes, parameters, and so forth. Libraries can be referenced from any report design and are used to store common report items. For example, a library may contain a corporate logo or a set of predefined styles. If the library is ever changed, all reports that reference the library are automatically changed.
When the report engine processes reports that contain library elements, the library must first be located. The library can be found in a settable resource folder or the in the same location as the report design. Currently in the integration, there is no master setting for the resource folder, so you will need to place your libraries in the same location as the report designs. One other option is to use the following PHP code:
//should only be set once will affect all users of the engine $birtReportEngine->getConfig()->setResourcePath(${here} . "/birtreportlib" ); $report = $birtReportEngine->openReportDesign("${here}/uselib.rptdesign"); $task = $birtReportEngine->createRunAndRenderTask($report);
In this example, we are using the report engine to get a handle to the engine configuration and then using this handle to set the resource path for BIRT libraries. When using this method, use caution, as all users of the engine are affected. It is likely that in future releases of the integration, this value will be placed in a properties file.
Summary
Hopefully, the integration with BIRT and the PHP/Java Bridge will be useful in your projects and, as is the case with most open source projects, we welcome your comments and contributions.