Using the BIRT Project emitter extension point

Extend your output capabilities

Built on top of the Eclipse framework, the Business Intelligence and Reporting Tools (BIRT) Project leverages the Eclipse plug-in framework to enhance and extend its capabilities. There are several different extension points defined in BIRT, with the major ones being the open data access point for retrieving data, the report item extension point for adding new report items to BIRT’s palette, the emitter framework for expanding BIRT’s output capabilities, the function script library for extending BIRT’s helper methods in BIRT script and expressions, and the aggregation extension point, which allows you to aggregate values of a data set. This article looks at building BIRT emitters to extend BIRT’s output capabilities.

John Ward, Independent consultant

John Ward is an independent consultant who specializes in BIRT, GWT, and e-commerce search and navigation solutions. John worked several years as a consultant, developing solutions that are based on BIRT. Before he began working as consultant, John was an assistant vice president for Citibank North America, where he managed the training MIS group and oversaw development of new technology-based training initiatives. He actively works with and tests BIRT, including development work that is 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.



18 September 2012

Also available in Portuguese

Introduction

Out of the box, BIRT gives you several output formats. Because BIRT was designed to be an online reporting tool its default output format is Hypertext Markup Language (HTML). However, you can generate Portable Document Format (PDF) files, and Microsoft® Office formatted Word and Excel® documents, and there are OpenDocument Format emitters in the source tree.

Yet, you may want to generate report output in a format that is not supported by BIRT out of the box. For example, you may want to generate raw Extensible Markup Language (XML) representations of report output that you can process further using Extensible Stylesheet Language Transformation (XSLT) or use for easy processing in unit testing. And by knowing how to work with the emitter extension point, you can enhance the existing emitters. For example, you can add security or encryption to the PDF output format or image support to the existing open source Excel output. This article guides you through creating some basic BIRT emitters using two different approaches in the emitter framework.

The BIRT emitter extension point

Emitters in BIRT are Eclipse extension point plug-ins. Emitters are called at render time only, after all report data is retrieved and the temporary RPTDOCUMENT file is created. As Figure 1 illustrates, this allows you to execute a report once and render to multiple formats without requiring the expensive process of retrieving data and building the RPTDOCUMENT.

Figure 1. BIRT emitters allow you to execute a report once and render to multiple formats
BIRT emitters allow you to execute a report once and render to multiple formats using emitters

There are two primary interfaces defined for creating report output: ContentEmitter and PageEmitter. The most commonly used interface for writing BIRT emitters is ContentEmitter, which uses an observer pattern similar to the one Simple API for XML (SAX) uses. In the case of BIRT’s emitter framework, the developer is only responsible for creating the observer. So a <report> tag is encountered from the render engine, and there is a corresponding startReport() method to handle that event. You also define an endReport() method to handle </report> tags. This is true for all BIRT report item types (grids, tables, rows, cells, labels, data, text, and extended item types), with all having a start method and most having an end method. There are some exceptions, which the example in this article illustrates. Fortunately, the interfaces already define these methods to override.

The ContentEmitter interface works great for structured output such as HTML. But when you need pixel-perfect output, you use the layout PageEmitter extension, which is how the PDF, Excel, and third-party Tribix emitters work. The basic idea is that there is an IPageDevice object that gets created at render time by the emitter framework, and that IPageDevice is responsible for creating IPages that are responsible for drawing the output using an X and Y coordinate system. IPages are observers, whereas the IPageDevice is a factory for IPages. There are a couple other factory objects that you need to create that the example for using the PageEmitter covers.


XML example using the content emitter

This example guides you through creating a basic XML output emitter. The XML output basically just outputs the element type encountered and the values inside. This is useful if you have some sort of XML processor that handles output, want to do some sort of XSLT transformation, or want to unit test report designs.

To create a basic XML output emitter, you need to do the following:

  1. Create a plug-in project.
  2. Define the emitters the plug-in provides.
  3. Test the new plug-in.

Creating a plug-in project

The first step in creating a basic XML output emitter is to create a plug-in project.

  1. In Eclipse, select File>New>Other (Figure 2).
    Figure 2. Eclipse File menu
    Eclipse File menu
  2. In the Wizards selection window, select Plug-Development>Plug-In Project (Figure 3).
    Figure 3. Wizards selection window
    Wizards selection window
  3. Give the project a name in the New Plug-in Project window (Figure 4).
    Figure 4. Plug-in Project window
    Plug-in Project window
  4. In the following window (Figure 5), you set up some basic metadata about the plug-in project. Clear the This plug-in will make contributions to the UI check box because the emitter will not make any user interface (UI) changes and will not require any Eclipse UI components.
    Figure 5. Plug-in Project Content window
    Plug-in Project Content window
  5. In the Templates selection window (Figure 6), clear the Create a plug-in using one of the templates check box.
    Figure 6. Templates selection window
    Templates selection window
  6. Click Finish.
  7. The first screen that displays for a plug-in project is the Manifest editor (Figure 7). You need to set a few things in this screen to begin developing the emitter.
    Figure 7. Manifest editor
    Manifest editor
  8. First, click the Extensions tab and click Add (Figure 8).
    Figure 8. Extension tab
    Extension tab
  9. In the Extension Point Selection window (Figure 9), use the org.eclipse.birt.report.engine.emitters filter. Be sure to clear the Show only extension points from the required plug-ins check box.
    Figure 9. Extension Point Selection window
    Extension Point Selection window
  10. When asked if you want to add the dependency for org.eclipse.birt.report.engine (Figure 10), click Yes.
    Figure 10. New plug-in dependency dialog box
    New plug-in dependency dialog box

Defining the emitters the plug-in provides

Now that you have a new plug-in project, you can define the emitters that this plug-in provides. An emitter plug-in project can provide multiple emitter output types. For example, a single image emitter can provide output to JPEG, Portable Network Graphics (PNG), Graphics Interchange Format (GIF), and bitmap formats.

  1. In the Extensions tab (Figure 11), click on the newly added emitter and fill in the appropriate metadata. Make sure you note the class name you use in this section because that is the class that handles all the emitter events. Also, whatever you enter for the format is what displays for the type of output to generate in any drop-down menu in the BIRT Report Designer or Sample BIRT Report Viewer. Be sure to set the ID field to something unique. In this example, I use the same name as the class. You can leave the default values for all the other fields.
    Figure 11. Extensions tab with new emitter
    Extensions tab with new emitter
  2. Create a new Java™ class with the package and name matching what you entered in the Manifest editor (Figure 12).
    Figure 12. New Java Class window
    New Java Class window
    This class should implement the org.eclipse.birt.report.engine.emitter.IContentEmitter interface (Figure 13).
    Figure 13. Implement Interfaces Selection dialog box
    Implement Interfaces Selection dialog box
  3. Use the code in the com.digiassn.blogspot.birt.emitter.XmlEmitter.java file as the implementation code (see Download).

Testing the new plug-in

Now that you've created the code for the emitter, you need to launch a test instance of Eclipse to make sure that everything is working.

  1. Open the Manifest.MF file and click the Launch an Eclipse Application link (Figure 14).
    Figure 14. Overview tab
    Overview tab
  2. If you go to Run>View Report in the instance of Eclipse that is running, a new entry called As XML is available (Figure 15).
    Figure 15. New As XML entry on the Run>View Report menu
    New As XML entry on the Run>View Report menu
  3. Using a simple report that has a single dynamic text element with 2 + 2 as the expression, the output looks like Figure 16.
    Figure 16. Example output
    Example output

Working with the layout page emitter

What if your desired output isn't structured like XML but uses a free-floating layout? Maybe you want something a little more pixel perfect, such as an image canvas or HTML using Cascading Style Sheets (CSS) for coordinates. The structured emitter won't work. The good news is that BIRT's emitter endpoint has classes for handling this situation under the org.eclipse.birt.report.layout.emitter package. The bad news is that working with this framework is more difficult because you need to implement several different classes.

The following example walks you through the classes you need to create to implement a PageEmitter-based plug-in for BIRT. This plug-in outputs a rendered JPEG image of the report output. It uses Abstract Window Toolkit (AWT) to create a canvas and draw the output from BIRT. AWT makes it easier to work with the colors and coordinate system passed in from BIRT. However, some of the techniques I use in this example are expensive in terms of time and memory.

You need to use a pagination setting that tells BIRT to output pages, whether it is page break or page size. The first class you need to implement is the PageEmitter (Listing 1). Unlike most classes in the emitter framework, this is actually an extended class rather than an implementation of an interface. This is a simple class with a simple responsibility. It is the class that you set in the Manifest as the implementation class for the emitter extension. It is only responsible for creating a PageDeviceRender class using its only available method, createRender(IEmitterServices). You can handle any configuration options for the output using the IEmitterServices class.

Listing 1. The PageEmitter class
package com.digiassn.blogspot.birt.layout.emitter;

import org.eclipse.birt.report.engine.api.EngineException;
import org.eclipse.birt.report.engine.emitter.IEmitterServices;
import org.eclipse.birt.report.engine.layout.emitter.PageDeviceRender;
import org.eclipse.birt.report.engine.layout.emitter.PageEmitter;

public class JpegPageEmitter extends PageEmitter {

@Override
public PageDeviceRender createRender(IEmitterServices services)
throws EngineException {
return new JpegPageDeviceRender( services );
	}
}

The next class you create is the PageDeviceRender (Listing 2). It too has simple responsibilities. It creates an IPageDevice when called. This class is actually an implementation of an IAreaVisitor, but you don't need to work with the IAreaVisitor to implement an emitter. It is much easier to work with extending the PageDeviceRender.

Listing 2. The PageDeviceRender class
 package com.digiassn.blogspot.birt.layout.emitter;
 
 import java.io.OutputStream;
 
 import org.eclipse.birt.report.engine.api.EngineException;
 import org.eclipse.birt.report.engine.api.script.IReportContext;
 import org.eclipse.birt.report.engine.content.IReportContent;
 import org.eclipse.birt.report.engine.emitter.EmitterUtil;
 import org.eclipse.birt.report.engine.emitter.IEmitterServices;
 import org.eclipse.birt.report.engine.layout.emitter.IPageDevice;
 import org.eclipse.birt.report.engine.layout.emitter.PageDeviceRender;
 
 public class JpegPageDeviceRender extends PageDeviceRender {
        		
 	private OutputStream output;
        		
        	/**
        	* Setup basic emitter services used throughout
        	* @param services
        	*/
        	public JpegPageDeviceRender(IEmitterServices services) {
        		try {
		this.output = EmitterUtil.getOuputStream( services, "report.jpg" );
        		} catch (EngineException e) {
        			e.printStackTrace();
        		}
        	}
        		
	/**
        	* Called by BIRT to create a page device. 
        	*/
        	@Override
public IPageDevice createPageDevice(String title, String author, String subject,
	String comments, IReportContext reportContext, IReportContent report)
        			throws Exception {
        		this.pageDevice = new JpegLayoutPageDevice(output);
        		return this.pageDevice;
	}
        		
        	/**
* Used to get the output format. This should match what is in the Manifest and Plugin.XML
        	*/
        	@Override
        	public String getOutputFormat() {
        		return "jpg";
        	}
}

The next class that you create is the IPageDevice (Listing 3). This class is called whenever a new page needs to be generated in BIRT report output. This class handles whatever the overall output format is and manages the individual pages to be added into the final output. In the JPEG emitter created here, there will only be one page handled, so it only needs to create a single ImageBuffer and Graphics object. You could employ other strategies, such as writing multiple images to an output directory and zipping all of them or appending to the end of an image file.

Listing 3. The IPageDevice class
package com.digiassn.blogspot.birt.layout.emitter;
        		
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
       		
import javax.imageio.ImageIO;
        		
import org.eclipse.birt.report.engine.layout.emitter.IPage;
import org.eclipse.birt.report.engine.layout.emitter.IPageDevice;
        		
/**
* This class is responsible for creating and managing pages during render.
* Since this is a simple example, this class will only handle a single page.
*
*/
public class JpegLayoutPageDevice implements IPageDevice {
        		
	//the AWT canvas used to draw the image
        	private Graphics2D canvas;
        	//output stream to write to
        	private OutputStream output;
        	//buffered image that will be written to the output
        	private BufferedImage bufferedImage;
        		
        	//set the scale of the image. 
        	double scale = 0.01;
        		
        	/**
        	* Constructor just initializes the output stream read from the 
        	* render options
        	* @param output
        	*/
        	public JpegLayoutPageDevice(OutputStream output) {
        		this.output = output;
        	}
        		
        	/**
        	* Write file on close. 
        	*/
        	@Override
		public void close() throws Exception {
			//use ImageIo to write out the file
        		ImageIO.write(bufferedImage, "jpg", output);
        	}
        		
        	/**
        	* Utility method to calculate the scale size. 
        	* @param size
        	* @return
        	*/
        	private int scale(int size)
        	{
        		return Math.round((float)(size * scale));
        	}
        		
        	/**
        	* Called by BIRT when a new page needs to be created
        	*/
        	@Override
        	public IPage newPage(int width, int height, Color color) {
        		//get the scaled width and height, and create an
        		//image buffer based on that size
        		int scaledWidth = scale(width);
        		int scaledHeight = scale(height);
        		bufferedImage = new BufferedImage(scaledWidth, 
        								scaledHeight, 

BufferedImage.TYPE_INT_RGB);
        		
        		//get the canvas to draw on from the buffered image
        		canvas = bufferedImage.createGraphics();
        		
        		//check the background color. If one is not set, default
        		//to white
        		if (color == null)
        		{
        			canvas.setBackground(Color.WHITE);
        		}
        		else
        		{
        			canvas.setBackground(color);
        		}
        		
        		//clear background color
        canvas.clearRect(0, 0, bufferedImage.getWidth(), bufferedImage.getHeight());
        		
        		//create a new page device
        		return new JpegPage(canvas, scale);
        	}
}

And the final class that you need to implement is the IPage (see the com.digiassn.blogspot.birt.layout.emitter.JpegPage.java file in the Download). This class is responsible for handling the drawing events, similar to how the ContentEmitter works. The exception is that there are only a handful of events, such as drawLine and drawText. Outside of some styling information, coordinates, and the final evaluated values to output, nothing else is provided.

Figure 17 is the output from the JPEG image emitter. There are some minor tweaks that you could make, but this should give you a general idea of how it works.

Figure 17. The output image from the JPEG output emitter
Example output

Conclusion

This article covered the basic requirements for implementing two different types of BIRT emitters: ContentEmitter and PageEmitter. You saw how to create an observer object to handle events that the ContentEmitter executes. These events create an XML document containing the values of the report output, and you can use them for any number of XML processing routines. You also saw how to create the factory objects used by the PageEmitter and how to create the observer for handling page render events. Using AWT you created a simple emitter that created JPEG images of BIRT reports. You should now have a basic foundation for creating BIRT emitters.


Download

DescriptionNameSize
Eclipse plug-in project source for the examplesemitterProjectSource.zip19KB

Resources

Learn

Get products and technologies

Discuss

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 Big data and analytics on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Big data and analytics, Open source
ArticleID=834908
ArticleTitle=Using the BIRT Project emitter extension point
publish-date=09182012