Monday, September 29, 2008

BIRT 2.3.1 - Adding Functions to the Expression Builder

BIRT 2.3.1. was released last week. It contains a lot of bug fixes and includes a couple of new features. True data set caching is now implemented, meaning if you use a data set more than once in a report, the data is only retrieve from your data source once. In addition a new extension is available to extend the expression builder.

BIRT uses expressions that are JavaScript snippets to build logic into reports. These expressions range from building highlight rules to creating computed columns and also include simple expressions that relate data elements to data set columns retrieved from a data source. To build an expression BIRT provides an expression builder gui. This builder has a set of prebuilt functions that include math, comparison, finance, and simple JavaScript functions.

With the 2.3.1 release of BIRT it is now possible to add your own custom functions to the expression builder. This is done by implementing the org.eclipse.birt.core.ScriptFunctionService extension point. With this extension point you can add new sub-categories under the BIRT Functions category and then within your category you can then add new functions.

To create a new category, add a category entry and supply a class that implements the IScriptFunctionFactory. This interface has one method that must be implemented which is named getFuncitonExecutor that returns an object that implements the IScriptFuncitonExecutor. The IScriptFunctionExecutor also has only one method that must be implemented, which is named execute and returns an object. This is the method where can enter your custom logic for your function that is to execute at runtime.

To illustrate the extension, assume you need a function that takes a string and returns the string reversed. You could do this by using the new extension point. The following is an example of the plugin.xml for a plugin that implements this logic.



<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
id="MyScriptFunctions"
name="MyScriptFuncitons"
point="org.eclipse.birt.core.ScriptFunctionService">
<Category
factoryclass="my.expression.functions.MyCategoryFactory"
name="MyCategory">
<Function
name="TestFunction">
<DataType
value="String">
</DataType>
<Argument
name="source">
<DataType
value="String">
</DataType>
</Argument>
</Function>
</Category>
</extension>
</plugin>




From the plugin.xml file you see that the extension point being implemented is the ScriptFunctionService. The Category element specifies the factory class and the name attribute is what shows up in the expression builder. The Category element can contain many Function elements which show up under the specific category and the name attribute for the Function element will be displayed in the expression builder GUI. Using the DataType and Argument elements you configure how many parameters are passed to your function and what the data types of the arguments and return types for the function are set to. In this example, the test function is called TestFunction and accepts a string and returns a string.
The Function element also supports variable arguments. Shown below is what the expression builder looks like with the above plugin enabled.




The Java code is as follows:
MyCategoryFactory class





package my.expression.functions;

import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.script.functionservice.IScriptFunctionExecutor;
import org.eclipse.birt.core.script.functionservice.IScriptFunctionFactory;

public class MyCategoryFactory implements IScriptFunctionFactory
{

public IScriptFunctionExecutor getFunctionExecutor( String functionName ) throws BirtException
{
return new MyCategory( functionName );
}

}




This class just implements the IScriptFunctionFactory interface that has only one method that returns an object that implements the IScriptFunctionExecutor interface.

MyCategory class


package my.expression.functions;

import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.script.functionservice.IScriptFunctionExecutor;

class MyCategory implements IScriptFunctionExecutor
{

private static final long serialVersionUID = 1L;

private IScriptFunctionExecutor executor;

MyCategory( String functionName ) throws BirtException
{
if ( "TestFunction".equals( functionName ) )
this.executor = new TestFunction( );
else
throw new BirtException( "my.expression.funcitons",
null,
"invalid.function.name" + "MyCategory." + functionName );
}


private class TestFunction implements IScriptFunctionExecutor
{

private static final long serialVersionUID = 1L;

public Object execute( Object[] args ) throws BirtException
{
if ( args == null || args.length != 1 )
throw new IllegalArgumentException( "The number of arguement is incorrect." );
if( args[0] == null){
return null;
}else{
StringBuffer sb = new StringBuffer( (String)args[0]);
return sb.reverse().toString();
}
}
}
public Object execute( Object[] arguments ) throws BirtException
{
return this.executor.execute( arguments );
}
}




This class uses a private class for each implemented function. In this particular case there is only one, TestFunction, which uses a StringBuffer to return a reversed string. It is important to realize that the execute method is only executed when the report is executed.



The source code for this example can be downloaded here.

This extension point also provide the JSLib element which allows a developer to provide a JavaScript file or directory containing many JavaScript files to be loaded when the script environment is created. The JSLib element contains one attribute called location which accepts a directory or file within the plugin to search for .js files. This element is very useful if you wish to add JavaScript objects to the JavaScript environment. Assume you have a JavaScript file with the following code



var preloadstring = "this string was preloaded";
function getPL(){
return preloadstring;
}




You can create a folder in your plugin that contains this file and then add the element JSLib to the plugin as follows.


.
.
.
</Category>
<JSLib
location="myjslibs">
</JSLib>
</extension>
</plugin>



The location attribute points to the myjslibs folder within the plugin. The above js file can be placed in the directory and it will automatically be pre-loaded by the script environment. Now the function getPL() can be called from any expression or JavaScript event handler.

Friday, September 19, 2008

Team Project Sets For BIRT

To start with the obvious, one of the advantages of an OSS project is that you use the source code to study, modify, or improve the software as you see fit.  In a large project, like BIRT, it can be difficult to get the source. Worse, the documentation for fetching the source changes with each build.  No one likes to writes docs, but having to re-write docs is particularly tedious.

In the Eclipse world there are two mechanisms that can be used to help tame these project structures and make it easier to get and build the source code from the version control systems.  The release engineering (releng) systems is a comprehensive system that is used to fetch, build and release the Eclipse projects, including BIRT.  Unfortunately, the releng process has a relatively high barrier to entry for someone that is un-familiar with the Eclipse process (its complicated).

The more user focused mechanism for getting a collection of projects from a version control system is the Team Project Set.  Developers can create a Project Set File (PSF) that describes the repositories, projects, and version tags that make up a collection.  Users can use the Import => Team => Team Project Set functionality in the Java Perspective to fetch the projects specified in the PSF.

I have created a simple program that uses the map files that are core to the RelEng process to build PSF files for the BIRT project.  I am working to get this added to the BIRT version control system, but I am hoping to get some feedback on the files before we commit this to the BIRT project.

Here are the direct links to the PSF files:

To use the team project sets files.  Download the file to your local machine.  Put the file into a Java project and open the Java Perspective.  Right click on the file an select Import.  There will be an option in the context menu to Import Project Set.. I don't recommend using that option since you can't run the import in the background.  
When you click Next, you will be taken to a dialog with the name of the .psf file in it.  You will probably want to select the "Run The Import in the Background" option.  You may get a pop-up dialog asking about the repository, if you do simply select the repositories and off it goes. 
The all_projects.psf will fetch close to 200 projects, so you may want to be careful choosing this option.

I currently have the PSF files set up to pull the current release candidates from BIRT and DTP respectively.

   BIRT_2_3_1_RC2_20080910
   DTP_1_6_1_M1_20080725

If you would like to pull a different version of the source you have a couple of options.  First you can pull the PSF files and then just do a string substitution on the version tags.  The other option is to use a subversion client (subclipse or subversive) to check out the conversion project from version control. The project that builds the PSf files is located on my subversion server at http://longlake.minnovent.com/repos/birt_example.

Next you need to pull the org.eclipse.birt.releng project from the dev.eclipse.org server using the /cvsroot/birt repository.  The project can be found under the Head/source folder, anonymous will work as the user name.  Remember you will want to pull the version of the releng project that matches the version you are trying to get from CVS.  

Now you have to open up the ConvertMapToPsf.java file and change lines 30 and 31 to use the appropriate CVS version tags.  You will need to lookup the tags from CVS.  The other popular tag you will want is the BIRT Ganymede Release 2.3.0:

    BIRT_2_3_0_Release_200806181122
    DTP_1_6_release_200806181028
Once you have the tags set, and you have gotten the org.birt.eclipse.releng project, simply run the ConvertMapToPsf.java file as an application and you will see the PSF files.

If you have any problems with the Team Project Sets let me know.  Hopefully we will have this in the product and a part of the BIRT web site soon, your feedback will be helpful.

EDITED TO ADD: Is anyone using stackoverflow.com?  I have been following this startup question and answer site for a while.  If you have questions or comments maybe you want to post them on the related post at stackoverflow.com.  Here is the Post

Thursday, September 11, 2008

Naming Exported files from the BIRT WebViewer

A common question we get on the BIRT news group is how to change the name of an exported document. For instance, exporting to PDF, the developer may wish to have more control on what filename is used for the export. Currently the name used is just the report name followed by the emitter extension (eg MyReport.pdf).

BIRT 2.3.1 which will be released later this month now supplies a solution to this problem. The example web viewer has a setting that can be added to the web.xml that allows you to specify a Java class that will be responsible for generating the name.



<!-- Filename generator class/factory to use -->
<context-param>
<param-name>BIRT_FILENAME_GENERATOR_CLASS
<param-value>org.eclipse.birt.report.utility.filename.DefaultFilenameGenerator
</context-param>








The class specified must implement the IFilenameGenerator interface, which has one method named getFilename. This method is passed four parameters.

baseName – Contains the base filename for the report, with no extension provided.
fileExtension – The extension for the selected operation (ppt for export to PowerPoint).
outputType – The operation being executed. More on this parameter later.
options – Specific options for the operation.

The instance of the IFilenameGenerator is called in multiple locations within the example viewer. When you export the report:



When you export the report data:



And when you use the /document servlet mapping, for example:

http://localhost:8080/WebViewerExample/document?__report=OrderDetails.rptdesign





This URL will run the report and download the rptdocument.

Suppose you wish to have the date in your filename, when exporting the report. To do this, create a class with the following code:



package my.filename.generator;
import java.util.Date;
import java.util.Map;
import org.eclipse.birt.report.utility.filename.*;
public class MyFilenameGenerator implements IFilenameGenerator{

public static final String DEFAULT_FILENAME = "BIRTReport";

public String getFilename( String baseName, String extension, String outputType, Map options )
{
return makeFileName( baseName, extension );
}
public static String makeFileName( String fileName, String extensionName )
{
String baseName = fileName;
if (baseName == null || baseName.trim().length() <= 0)
{
baseName = DEFAULT_FILENAME;
}

// check whether the file name contains non US-ASCII characters
for (int i = 0; i < baseName.length(); i++) {
char c = baseName.charAt(i);

// char is from 0-127
if (c < 0x00 || c >= 0x80) {
baseName = DEFAULT_FILENAME;
break;
}
}

// append extension name
if (extensionName != null && extensionName.length() > 0) {
baseName += (new Date()).toString() + "." + extensionName;
}
return baseName;
}
}





If you check the source, you will notice this is just the default class with one modification.


baseName += (new Date()).toString() + "." + extensionName;


Which just inserts the date into the output.

You can also check the operation type if you wish to set the name based on the operation. Currently the available options for outputType are:

IFilenameGenerator.OUTPUT_TYPE_EXPORT – When exporting report to one of the supported formats.
IFilenameGenerator.OUTPUT_TYPE_DATA_EXTRACTION – When exporting report data.
IFilenameGenerator.OUTPUT_TYPE_REPORT_DOCUMENT – When using the document servlet mapping.

This example is located here.

Vincent Petry from the dev team has also uploaded a Birt Viewer 2.3 User Reference, which describes the settings and parameters available with the example web viewer. This document is informative and is located here.

If you wish to build your own version of the filename generator, make sure to include viewservlets.jar from the WebViewerExample\WEB_INF\lib directory in your build path.