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.

35 comments:

Matthew said...

thank u r information

it very useful

HAI said...

thank u r information

it very useful

u r blog Is very nice

Joshua said...

Where is plugin.xml located? Thanks.

Jason Weathersby said...

Joshua,

It is in the zip file you can download from the link in the post.

Jason

Joshua said...
This comment has been removed by the author.
Joshua said...

Umm..where do you put those files including class file, javascript and plugin.xml to make the new function(s) available to both Eclipse and then in web viewer app?

Scott Rosenbaum said...

You need to export your plug-in project, and put the resulting jar into the plugins directory.

On the first page (Overview) of the plugin editor, the lower right corner has exporting. Select the Export Wizard. The export wizard will prepend your jar with /plugins/file_name.jar

Once you have done that you will need to re-start Eclipse. If you want to test your plugin without the re-start, the you will need to do a runtime workbench.

Joshua said...

Ah ha. So first, I need to create a new eclipse plugin project and then expand the zipped file into this project before exporting into eclipse/plugins/{name}.jar? In Web Viewer app, I can just drop this jar in plugins/ as well and the reports will be able to refer to new functions after a restart?

keith_c06 said...

Building the custom functions for the Expression builder is great, but how do I get them to be recognized when I deploy the report?
I've tried to deploy the reports using the "WebViewExample" from the birt-runtime-2_3_1 download, but I always get a TypeError: Cannot find function VMCEFormatName. I've added the exported jar into the tomcat/webapps/birt-viewer/WEB-INF/platform/plugins directory to no avail. Any ideas?

Jason Weathersby said...

Can you email me your exported jar?

SpinGrrl said...

When I tried exporting this plugin, I get a bunch of errors stating that org.eclipse.birt.core.script.functionservice cannot be resolved. Am I missing a step?

Wei-Jen a.k.a. Larry said...

The plug-in project has to be exported to
"Deployable plug-ins and fragments."
otherwise it won't work.

I tried to change TestFunction to TestFunction2, in Mycategory.java and plugin.xml. Then the report designer couldn't find it. Is there any other config file I have to change?

By the way, since BIRT 2.5.x have one more argument for execute (public Object execute( Object[] args, IScriptFunctionContext arg1)), birt has a null pointer error when I click on "BIRT Functions"

Larry

Jason Weathersby said...

Larry,

Start eclipse with the -clean option. I just tried this with 2.5 and I did not have an issue.

Jason

Wei-Jen a.k.a. Larry said...

Jason,

eclipse -clean works. This is great.

Now I have a further problem. I put the jar in birt runtime (2.5.0), which is in a jboss server running in linux.

I put the jar file in WEB-INF/platform/plugins, and restart the server. But the report cannot find MyCategory. Is there something like "-clean" I have to do for the server? Or do I miss anything else?

By the way, I would like to say BIRT is great and very flexible for making many kinds of reports, and there is always ways to "go around" when I have problems... And I really appreciate all your help.

Jason Weathersby said...

Larry,

In the WEB-INF/Platform directory there is a config directory and a workspace directory. Is the workspace directory empty? If not shut down app server and delete contents. In the config directory delete every bu the config.ini.

Jason

Parapente said...

If you wanna make it work with Web Viewer 2.5, you must remove dependency on org.eclipse.ui and org.eclipse.ui !

Anonymous said...

Does somebody make it work with birt 2.5 in the web viewer? If so, please post how to make it work

jucaalpa said...

Does somebody make it work with birt 2.5 in the web viewer? If so, please post how to make it work

Jason Weathersby said...

Take a look at this post:
http://www.birt-exchange.org/devshare/designing-birt-reports/1101-example-plugin-for-expression-builder/#description

Jason

jucaalpa said...

Thanks Jason. It works perfectly.

Juan Carlos

Fidelis said...

Hi all

I've made your example but there no way to make it works in the Report Engine.

I've already set up some runtime options and put the jar into the ReportEngine\plugin folder.

The Report Engine log show me
"org.mozilla.javascript.EcmaError: ReferenceError: "MyCategory" is not defined."

Do you have any idea what I am doing wrong

Regards

Jason Weathersby said...

Did you use the example from:
http://www.birt-exchange.org/devshare/designing-birt-reports/1101-example-plugin-for-expression-builder/#description

Fidelis said...

hi Jason

yes, I am using this example.

It works fine using eclipse but in the ReportEngine it does not work.

Should I make anything more? I am only generating the jar and paste in the plugin foder.

Thanks for the help

Jason Weathersby said...

Make sure it does not have any dependecies on the ui in the plugin manifest.

Fidelis said...

I am not using that dependency.

Why does the eclipse can see my plugin but the ReportEngine can't?

thanks

Jason Weathersby said...

The ReportEngine should see it fine. Can you email me the manifest file?
jasonweathersby at windstream.net

satish said...

thanks for this short and important post

brieweb said...

Can you use java in your expression?

Jason Weathersby said...

in any BIRT expression you can always call Java like:
importPackage(Packages.my.package)
var tst = new MyClass();
tst.callmethod();

Sangy said...

can we access application context in the Expression Builder?

Jason Weathersby said...

yes. Use reportContext.getAppContext().get("variablename");

Sangy said...

Hi Jason,
Thanks for the quick reply!

Just wondering if its possible to populate a report parameter of list type from a list (Java list) saved in application context?
Reason I want to do so is because I am getting this list data from a web service.

Jason Weathersby said...

Sure this can be done. You can create a scripted datasource/dataset and assign the parameter to this dataset or you can impelment one of the parameter scripts to do this (http://www.eclipse.org/birt/phoenix/project/notable2.5.php#jump_2).

Jason Weathersby said...

http://www.eclipse.org/birt/phoenix

/project/notable2.5.php#jump_2

Veerendra Kumar said...

Hi,

I have created Script Functions Extension in Birt. Category is shown in Expression Builder but not the function is not displayed.

I have created jar file using Plug-in Project and Export Wizard. All My Class files and Libraries are bundled. All My Class files are within Bin. I am getting class not exception.

I am using JRE 7. Please find the plugin.xml below.