Using Eclipse BIRT extension points

Aggregation

Get to know the BIRT extension point model by creating a basic aggregation extension in BIRT using the new V2.3+ extension model.

John Ward (johnw@innoventsolutions.com), Senior Consultant, Innovent Solutions

John Ward is a consultant for Innovent Solutions, specializing in BIRT and e-commerce search and navigation solutions. Prior to that, John was an assistant vice president for Citibank North America, managing the training MIS group and overseeing developing of new technology-based training initiatives. He actively works with and tests BIRT, including development work based on BIRT reports and the BIRT APIs. He is the author of Practical Data Analysis and Reporting with BIRT and maintains The Digital Voice blog at http://digiassn.blogspot.com.



01 December 2009

Also available in Japanese

This article provides an introduction to the Business Intelligence and Reporting Tools (BIRT) extension point model by walking through the creation of an aggregation extension in BIRT V2.3.x and V2.5.x. In older versions of BIRT, you created aggregation extensions by extending the org.eclipse.birt.data.aggregation extension point, which would add a neat little function to a global object called Total that you could use throughout your report in any expression — similar to how the scripting functions extension point works.

The aggregation extension has changed between BIRT V2.2 and V2.3, however. The new way is a bit more complex, but the result is that you get a nice aggregation in the drop-down list of the Aggregation widget, with nice text boxes for the parameters and expressions. When you create this extension, you can access the result as a column binding in your table.

At a high level, the new aggregation extension point consists of an object that is an extension of the IAggregationFactory interface. You overload the methods in this interface to do three things:

  • Initialize your factory (in the constructor)
  • Provide a list of aggregations provided by the factory (in the form of a list containing the actual instances of the aggregation objects)
  • Return a single instance of an aggregation object

Each instance of an aggregation needs to implement the IAggrFunction interface. There are a number of self-explanatory methods to implement, such as getName, getDataType, and getParmaeterDefn, along with other methods that aren't quite so obvious. For example, the getNumberOfPasses() and getType() methods are related. The getType() method specifies how this aggregator is executed or what type it is. There are two types of aggregations: the SUMMARY_AGGR, which means that the aggregation is only calculated for the summary (like in the header or footer of a table), and a RUNNING_AGGR, which means that the aggregation is calculated for every row in a table or footer. The getNumberOfPasses() method shows the number of passes needed to get a result. All the rank-based aggregators, such as TopNpercent, PercentSum, and Percentile, return a value of 2, while the rest return a value of 1.

The actual implementation of the IAggrFunction interface must return an extension of the Accumulator class in its newAccumulator() method. The Accumulator is responsible for doing the actual calculation. You have a few default methods to override, the most important of which is onRow(), which is called for each row in the table. With this method, you parse your arguments to your function and do your calculation. For a SUM, you would just add to some stored number; for an ave, you could either stash into some sort of list for storage or add to a running total and keep track of the number of times called. However you do your running calculation, it's done here. And the getValue() generates get the final value, or current value, of your calculation. So, in the case of a SUM operation, you would return the total/count operation. In a running aggregator, you would just return the running value.

The example in the next section walks you through creating a simple Word Count aggregator. This aggregation takes all the sentences in a column and counts the words, returning an integer value containing the number of words for that column. This is a rare aggregation need, which is why one doesn't already exist. For these exercises, I recommend that you use the Eclipse BIRT All-in-One distribution (see Resources).

Creating a new aggregation plug-in

To create your new aggregation plug-in:

  1. Create a new plug-in project by clicking File > New > Other. Expand the Plug-In Development folder, and then click Plug-in Project.
  2. In the New Plug-in Project window (Figure 1), provide a unique name for the project in the ID field, and complete the Version, Name, and Provider fields with the appropriate information.
    Figure 1. Project properties
    Image showing project properties
  3. Specify Java™ 2 Platform, Standard Edition (J2SE) V1.5 as the execution environment to keep base compatibility with BIRT.
  4. In the Options area, clear the This plug-in will make contributions to the UI check box. By clearing this check box, you limit the number of templates available. You will not use templates for this project.
  5. In the Rich Client Application area, select No, as this is not an Eclipse rich client project. Click Next.
  6. On the Templates page, clear the Create a plug-in using one of the templates check box, then click Finish. If you are not already in the Plug-In development perspective, you will be prompted to open it.

Setting up the extension point

With the new project created, you're ready to set up your extension point. To do so:

  1. With the manifest window open, click the Extensions tab, then click Add.
  2. In the Extension Point Selection window (Figure 2), clear the Show only extension points from the required plug-ins check box.
    Figure 2. Create a new extension
    Create a new extension
  3. In the Extension Point filter field, type org.eclipse.birt.data. The aggregation extension point appears. When you add this extension point, you are prompted to add the dependencies.

Creating the necessary aggregation classes

Now that you have added the aggregation extension point, you need to add a new AggregationFactory. To do so, right-click the aggregation extension point you just added, point to New, then click AggregationFactory, as shown in Figure 3. Note that you do not add an aggregation; the aggregation is the older extension method, which has been deprecated. The AggregationFactory is the main point of entry for this plug-in, and it is responsible for registering the types of aggregations available and creating instances of those aggregations at runtime.

Figure 3. Create a new AggregationFactory
Image showing a new AggregationFactory

When you have added the definition for the factory, you get a text item with the fully qualified package and class name for it. As shown in Figure 4, the factory is named com.digiassn.blogspot.birt.aggregators.wordcount.WordCountFactory. Keep in mind that it is possible for this factory to register and create more than one type of aggregation, but you do this in the body of the code. In the Extension Element Details area, type or browse to the name of your factory class, then click the class hyperlink next to the text box.

Figure 4. Create new factory class
Image showing a new factory class

The New Java Class wizard will already have the appropriate package and class information. Verify the settings on the Java Class page (see Figure 5), then click Finish.

Figure 5. Factory class properties
Image showing factory class properties

Your class source code opens, as shown in Figure 6. If you have trouble finding the org.eclipse.birt.* imports, be sure to go back and save your changes in the manifest window. Remember that you need to add the necessary inherited abstract methods for your class.

Figure 6. Factory class skeleton
Class skeleton

Your class has three functions: a constructor, a method called getAggregations() that returns an IAggrFunction, and a method called getAggregations() that returns a list. The getAggregations() method returns a list of IAggrFunction types to the caller so the caller knows what type of individual aggregations this factory can produce. It is the responsibility of the caller to iterate over the list and call the IAggrFunctions methods to get descriptions. For the factory, these descriptions are out of scope; it is only the responsibility of the factory to return and maintain this list.

The getAggregation() method is passed in a name of the aggregation. It retrieves a name and provides an IAggrFunction result. Listing 1 shows the example factory.

Listing 1. The finished factory class
public class WordCountFactory implements IAggregationFactory {
	HashMap<String, IAggrFunction> aggregateMap;
	
	public WordCountFactory() {
		aggregateMap = new HashMap<String, IAggrFunction>();
		
		BasicWordcount wordCountAggregation = new BasicWordcount();
		
		aggregateMap.put(wordCountAggregation.getName(), wordCountAggregation);
	}

	public IAggrFunction getAggregation(String aggregationName) {
		
		return aggregateMap.get(aggregationName);
	}

	public List getAggregations() {
		
		return new ArrayList<IAggrFunction>(aggregateMap.values());
	}

}

Creating the individual aggregation description class

Next, create a new package called com.digiassn.blogspot.birt.aggregators.implIn in the src folder. Beneath this new package, create a class called BasicWordCount. In the Java Class window, select the Inherited abstract methods check box so that this class will inherit the org.eclipse.birt.data.engine.api.aggregation.IAggrFunction interface, as shown in Figure 7.

Figure 7. Create a new IAggrFunction implementation
Image showing the new IAggrFunction implementation

The IAggrFunction class has two responsibilities: It describes itself as an aggregation that your factory can create, and it creates instances of an Accumulator class that will do the actual work for your aggregation. Listing 2 shows the code.

Listing 2. The finished aggregate function and Accumulator class
public class BasicWordcount implements IAggrFunction {
	
	private final static String sDescription = 
		"This aggregation will count all words in a column and return the count.";
	private final static String sName = "Word Count";
	private final static String sDisplayName = "Basic Word Count Aggregator";
	
	public int getDataType() {
		return DataType.INTEGER_TYPE;
	}

	public Object getDefaultValue() {
		return new Integer(0);
	}

	public String getDescription() {
		return this.sDescription;
	}

	public String getDisplayName() {
		return this.sDisplayName;
	}

	public String getName() {
		return this.sName;
	}

	public int getNumberOfPasses() {
		return 1;
	}

	public IParameterDefn[] getParameterDefn() {
		IParameterDefn paramDef = new IParameterDefn() {
			public boolean supportDataType(int paramType) {
				if (paramType == DataType.STRING_TYPE)
				{
					return true;
				}
				
				return false;
			}
			
			public boolean isOptional() {
				return false;
			}
			
			public boolean isDataField() {
				return false;
			}
			
			public String getName() {
				return "StringColumn";
			}
			
			public String getDisplayName() {
				return "String Column";
			}
			
			public String getDescription() {
				return "A column expression that is a String";
			}
		};
		
		IParameterDefn[] parameterDefinitionArray = new IParameterDefn[] 
								{paramDef};
		return parameterDefinitionArray;
	}

	public int getType() {
		return IAggrFunction.SUMMARY_AGGR;
	}

	public boolean isDataOrderSensitive() {
		return false;
	}

	public Accumulator newAccumulator() {
		return new Accumulator()
		{
			int sum;
	
			@Override
			public Object getValue() throws DataException {
				return new Integer(sum);
			}

			@Override
			public void onRow(Object[] incomingStrings) throws DataException {
				String localString = (String) incomingStrings[0];
				
				sum += localString.split(" ").length;
			}
			
		};
	}
}

Most of the methods in this class simply describe various aspects of the aggregation, such as data type, title, and description information, to show up in the BIRT Aggregation widget drop-down list. Three aspects deserve further explanation, however. First is the getParameterDefn() method. This method returns an array of IParameterDefn objects, which is where you define the parameters that your aggregation is expecting. An aggregation can have multiple parameters, which is why this is returned as an array. This method simply describes to the BIRT engine the parameters and their types. In the case of this example, you only have one parameter expected — the column expression — which will be one string.

So, if parameters are described in the getParameterDefn() method, where are they passed in to do the actual work? The IAggrFunction object also serves as a factory for the Accumulator class, which should be created in the newAccumulator method. Accumulator is the class doing the actual work in the aggregation. It has two methods you override: the getValue() and onRow(). For every row being processed in BIRT, the data binding will call onRow() if you are using this aggregation. As a parameter, onRow receives an array containing the aggregation parameters described by getParameterDefn(). In a more robust scenario, you can call getParameterDefn() and test that the parameters being passed into onRow() match the definition. This simple example, however, skips that step. The onRow() method is also responsible for doing the processing. In the above example aggregation code, it simply adds to a running sum the number of words in the string. When the report is ready to display the value, it calls the getValue() method.

The only other element that requires further explanation is the IAggrFunctions getType() method. The two types of aggregations —SUMMARY_AGGR and RUNNING_AGGR— define what type of aggregation this is, whether it appears in the header or footer of a report table or shows in every row, and whether to display a running total or the value of averages as they are calculated.


Testing the plug-in

You can easily test this plug-in without deployment in the way that you would test any Eclipse plug-in: by launching another instance of Eclipse. The easiest way to do this is from the Overview tab in the plugin.xml/manifest window. On this tab, in the Testing area, click the Launch an Eclipse application link, as shown in Figure 8.

Figure 8. Testing your plug-in
Image showing the testing of your plug-in

Figure 9 shows a simple report against which to test the plug-in. Insert an aggregation component into the footer of this report.

Figure 9. Add aggregation to a report
Screenshot showing adding aggregation

In the Aggregation Widgets drop-down list, select the Basic Word Count aggregator and input the column expression to point to a column that contains the sentences you want to count, as shown in Figure 10.

Figure 10. The Aggregation Builder
Image showing the Aggregation Builder

Run the report, and verify the results. Figure 11 shows the resulting report.

Figure 11. Report result
Image showing the report result

Conclusion

Using the example in this article, you now have a fairly good introduction to the BIRT extension point framework. This will get you started on building some aggregations to use in your reports, and it showcases BIRT's ability to extend beyond its out-of-the-box functionality. Be sure you use the Export Wizard in the plugin.xml/manifest window to export your plug-in and copy that plug-in into the Plugins folder of your BIRT target environments — be they Web applications or other BIRT designer environments. Otherwise, your deployed reports will not run, and anyone editing your reports will need to remove the aggregation definitions.

Resources

Learn

Get products and technologies

Discuss

  • The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
  • The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
  • Participate in developerWorks blogs and get involved in the developerWorks community.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Open source on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source
ArticleID=449575
ArticleTitle=Using Eclipse BIRT extension points
publish-date=12012009