Thursday, August 23, 2007

BIRT: Writing an Emitter

Another article I have had on the back burner for the past few months is for writing emitters using BIRT. Emitters are one of the many extension points in BIRT. Emitters are the output rendering mechanism in BIRT. When you run a BIRT report and get the HTML output from the Report Engine, that HTML is created using the BIRT HTML Emitter. Ditto for the PDF output. There are even Office emitters out there.

The way that I typically look at Emitters is that they are a mechanism for getting BIRT report output to some output mechanism. That output can be a file, a stream, and an IPC listener, whatever. This gives BIRT the ability to serve as more than just a Report Engine, but possibly as a middleware also. Why would you do this? Despite the added bloat, it allows you take advantage of the BIRT internal mechanisms for sorting, aggregating, formatting, and a filtering data. Of course, more often than not, you will just use an Emitter to output file formats.

So how do emitters work? An emitter is an Eclipse Plug-in, and when writing one, you need to set up as an Eclipse Plug-In project. Since it is an Eclipse Plug-in, it requires that you set up the proper entries in the plug-in.xml and Manifest.MF files. This can be a bit tedious to do, and in past experiences required a bit of trial and error on my part.

Since it is an Eclipse Plugin, there are two classes that need to be created. The Activator, which is usually automatically created when you create a new plug-in project, and the actual emitter. The Activator extends the org.eclipse.core.runtime.Plugin class. The code for this is automatically generated upon project creation. You will only need to be sure that the plug-in.xml file is pointing to the correct Activator.

The Emitter class itself is an extension of the org.eclipse.birt.report.engine.emitter.ContentEmitterAdapter class. This is where all the magic happens. The emitter class will simply implement certain methods based on the requirements of the emitter. In the following example emitter code, I wrote an emitter that will generate a very generic XML file using JaxB.

package com.        .birt.emitter;

import java.io.OutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.PropertyException;

import org.eclipse.birt.report.engine.content.IBandContent;
import org.eclipse.birt.report.engine.content.IReportContent;
import org.eclipse.birt.report.engine.content.IRowContent;
import org.eclipse.birt.report.engine.content.ITextContent;
import org.eclipse.birt.report.engine.emitter.ContentEmitterAdapter;
import org.eclipse.birt.report.engine.emitter.IEmitterServices;

import com. .birt.emitter.xml.ObjectFactory;
import com. .birt.emitter.xml.Root;
import com. .birt.emitter.xml.Root.Office;
import com. .birt.emitter.xml.Root.Office.Employees;

public class XMLEmitter extends ContentEmitterAdapter {
private ObjectFactory xmlObjectFactory;
private Root xml;
private Office currentOffice;
private OutputStream reportOutputStream;

@Override
public void initialize(IEmitterServices service) {
super.initialize(service);

//initialize my object factory for the XML file and create a root element in the XML file
this.xmlObjectFactory = new ObjectFactory();
this.xml = xmlObjectFactory.createRoot();

//create an OutputStream to output to the console
reportOutputStream = service.getRenderOption().getOutputStream();
}

@Override
public void startRow(IRowContent row) {
super.startRow(row);

//When we encounter a new row, and it is a HEADER for a group, we need
//to create a new office element
if (row.getBand().getBandType() == IBandContent.BAND_GROUP_HEADER)
{
this.currentOffice = xmlObjectFactory.createRootOffice();
}
}

@Override
public void endRow(IRowContent row) {
super.endRow(row);

//Once we encounter the end row and this is a HEADER row, we need to add
//this to our XML structure under the Office sections
if (row.getBand().getBandType() == IBandContent.BAND_GROUP_HEADER)
{
xml.getOffice().add(currentOffice);
}
}

@Override
public void end(IReportContent report) {
super.end(report);

//At the end of our report generation, create a new JaxB Marshaller object, and output the
//formatted output to the console
try {
JAXBContext jaxContext = JAXBContext.newInstance("com. .birt.emitter.xml", Root.class.getClassLoader()) ;
Marshaller xmlOutputWriter = jaxContext.createMarshaller();
xmlOutputWriter.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

xmlOutputWriter.marshal(xml, reportOutputStream);
} catch (PropertyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (JAXBException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

@Override
public void startText(ITextContent text) {
super.startText(text);

//If this is a new text element (data in a row), then we need
//to go ahead and add this as our current office name if this is a header.
//If this is not a header, then we know it is employee information, and we
//need to add this to our list of office employees
IRowContent row = (IRowContent)text.getParent().getParent();
if (row.getBand().getBandType() == IBandContent.BAND_GROUP_HEADER)
{
currentOffice.setName(text.getText());
}
else
{
Employees currentEmployee = xmlObjectFactory.createRootOfficeEmployees();

currentEmployee.setName(text.getText());
currentOffice.getEmployees().add(currentEmployee);
}
}
}


You will notice it uses a very SAX type of processing, where each element gets a Start and End method. Each of these element equates to a type of designer element. In the above example, we are only looking for new rows, and new text elements. This emitter makes the following assumptions:

-That the report design file is a single table

-That the table is grouped by an Office ID

-That the data element in the Detail row only contains the Employees name.

So in other words, this particular emitter is not a general purpose emitter, it is designed with a specific report design file in mind.

Setting up the Emitter is another task in itself. I used the Eclipse 3.3 Plug-In configuration editor to set mine up, however, you can manually edit yours by hand. The first thing I did was to configure the general purpose things, such as name and ID, and maek sure the activator was correct.

Figure 1. Emitter configuration

Next I need to configure the Dependencies. In this case, the BIRT Report Engine and the Eclipse Core.

Figure 2. Dependencies

Next, I specified the packages to export during build needed for Runtime. I specified the 3 packages I need to export in my Emitter, the package with the Activator, the package with the emitter itself, and the package with the JaxB generated classes. In the classpath, I specify the jars I need for JaxB to work properly.

Figure 3. Runtime Exported Classes and Jars

Next, I specify how to configure my extensions. I create a new emitter extension, specify the class to use, the format, which is important when I specify the output format for BIRT to use, I will use this string. I specify a generic MIME type to use, and specify an ID, in which I used my package name. I also specify no-pagination, which is important if you are building a BIRT emitter that will support multiple pages, such as the PDF emitter. This will influence the behavior of the document generator inside of BIRT, and will add more legwork to the emitter.

Figure 4. Extension configuration

That’s pretty much it. Now, when I want to test this, I right-mouse click on my project and specify Export, and Deployable Plug-Ins and Fragments. I usually export to my BIRT Runtime folder for testing, and will write a few Unit Tests to test the execution of the emitter. Below is an example of the output I get from my emitter.



<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Root xmlns="http://www.example.org/OfficeLayout">
<Office>
<name>1</name>
<Employees>
<name>Murphy, Diane</name>
</Employees>
<Employees>
<name>Patterson, Mary</name>
</Employees>
<Employees>
<name>Firrelli, Jeff</name>
</Employees>
<Employees>
<name>Bow, Anthony</name>
</Employees>
<Employees>
<name>Jennings, Leslie</name>
</Employees>
<Employees>
<name>Thompson, Leslie</name>
</Employees>
</Office>
<Office>
<name>2</name>
<Employees>
<name>Firrelli, Julie</name>
</Employees>
<Employees>
<name>Patterson, Steve</name>
</Employees>
</Office>
<Office>
<name>3</name>
<Employees>
<name>Tseng, Foon Yue</name>
</Employees>
<Employees>
<name>Vanauf, George</name>
</Employees>
</Office>
<Office>
<name>4</name>
<Employees>
<name>Bondur, Gerard</name>
</Employees>
<Employees>
<name>Bondur, Loui</name>
</Employees>
<Employees>
<name>Hernandez, Gerard</name>
</Employees>
<Employees>
<name>Castillo, Pamela</name>
</Employees>
<Employees>
<name>Gerard, Martin</name>
</Employees>
</Office>
<Office>
<name>5</name>
<Employees>
<name>Nishi, Mami</name>
</Employees>
<Employees>
<name>Kato, Yoshimi</name>
</Employees>
</Office>
<Office>
<name>6</name>
<Employees>
<name>Patterson, William</name>
</Employees>
<Employees>
<name>Fixter, Andy</name>
</Employees>
<Employees>
<name>Marsh, Peter</name>
</Employees>
<Employees>
<name>King, Tom</name>
</Employees>
</Office>
<Office>
<name>7</name>
<Employees>
<name>Bott, Larry</name>
</Employees>
<Employees>
<name>Jones, Barry</name>
</Employees>
</Office>
</Root>

12 comments:

Anonymous said...

Thanks for posting this example John. You make working with emitters look easy and the output to XML is asked for quite a bit.

Virgil

Anonymous said...

You said "An emitter is an Eclipse Plug-in". Does this mean that you can only use Emitters within Eclipse? What if I need to emit a report to a file, not to the screen?

John Ward said...

Anonymous,

Not at all. Eclipse is a framework and development platform.

What you would do is develop the emitter in Eclipse using the Eclipse Plug-in project. When you deploy, you deploy the plugin to the BIRT runtime, which already handles the instantiation of the Eclipse platform for you, which loads your plugin. Your emitter can output to anything, the screen, a stream, or a file. If your developing a custom emitter, in the initialize, and end methods, you would check the render options class for the output stream or file option, and output based on that.

In the above example, I use the output stream. But there is also a file option if you pass in a file argument to the runtime.

Unknown said...

Hi John,
I need to customize the PDF output of my report.
For this, Could u pls tell me, how to customize the pdf that is generated? What are the java files we need to modify?
what are the java files that are responsible for rendering the PDF output.
Is it necessary to write our own Emitter class to include any new elements to appear in the PDF...???
Pls respond asap.

-Ganesh

Anonymous said...

@vijay - try itext

-QBiT

Anonymous said...

Hi John,
Does this mean you have to know the scheme for a BIRT report into order use this emitter?
I want to an emitter to be able to handle more BIRT reports dynamically into XML documents. Is this possible?
Pls respond asp.
--Frances

John Ward said...

No, you can do dynamic XML no problem. It would be a little more complicated than this example, so in the start and end events for each level (row, cell, data, etc), you would need to keep track of what XML element you are mapping to.

Priyanka said...

Hi John,
Iam a Software Developer trainee recently joined an IT company and totally new to BIRT tool. I need to know the exact procedure of converting the .rptdesign report file into XML format.Above you have given the explanation, but sorry to tell im not understanding how to do it.This is the task i need to perform, which is assigned to me from my Team leader..
plz help me in this coz this will make me to save my job..
plz plz plz

Thanks,
Priyanka

Rohit said...

hi, i want an emitter which renders html5 output. can u guide me thro this ??

Rohit said...

hi, i want to develop html5 report rendering extension for our organisation's application. So, please can u guide me thro' this ???

Anonymous said...

hi john,
i want to develop html5 report rendering extension, so please could you guide me through this ?

Anonymous said...

Hi
Can you pls guide on how to write HTML emitter