Mark Lorenz on Technology

Wednesday, March 01, 2006

More BIRT Tips for Java Developers

Last month, I wrote some tips for using BIRT from Eclipse to do your report generation. I promised more BIRT tips, so here they are:
  • Control page breaks
Thanks to Jason Weathersby for this tip.
For example, you may want to only have a page break after the first page (or last or whatever conditions). To do this you would:
  • Select the report page and go to the Script page. Add "firstPage = 0;" on a separate line.
  • Add a table to your report.
  • Add a group to your table.
  • Select the group header row and go to the Script page.
  • Go to the onCreate scripting page and add this logic:
          if( firstPage > 0 ) {
this.getStyle().pageBreakBefore = "Always";
} else {
this.getStyle().pageBreakBefore = "Avoid";
firstPage = firstPage + 1;
}
That's it! Now all pages after the first will have a page break before them.

Note: This does not work with a List. Believe me, I tried. So unless you just want the learning experience of a hard spanking, stick to Tables.

Note: To force page "breaks" for HTML output, use the Page Break Interval field. E.g. you could specify a new page every 50 lines. This is necessary because browsers can be resized, so the number of lines can change.

  • Set up your Master page properly
Thanks to Diana Peh for this tip.
The Master page of your report has some rules that must be followed:
  • You can insert only one top-level element in the header or footer. By default
    there's already a date element in the footer. You must delete the date element if you want to add something else (e.g. a page element).
  • If you want to insert multiple elements, delete the date element first,
    insert a grid, then insert as many elements as you want in the grid.
  • No data set fields can be used on the Master page.

  • Avoid specify dimensions in percentages (%)
Percentages for width or height does not work as you might expect. E.g. resizing a browser window will affect the artwork or chart that is sized by percentages. Use inches or some other metric.

A small Report framework to reuse

This isn't exactly a tip, but more of a leg up. Here's an abstract Report class that you can set up a small framework to support BIRT reporting:


/**
*
*/
package com.yourcompany.domain.reporting;

import java.security.InvalidParameterException;
import java.util.HashMap;

import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.EngineConstants;
import org.eclipse.birt.report.engine.api.EngineException;
import org.eclipse.birt.report.engine.api.HTMLCompleteImageHandler;
import org.eclipse.birt.report.engine.api.HTMLEmitterConfig;
import org.eclipse.birt.report.engine.api.HTMLRenderContext;
import org.eclipse.birt.report.engine.api.HTMLRenderOption;
import org.eclipse.birt.report.engine.api.IReportRunnable;
import org.eclipse.birt.report.engine.api.IRunAndRenderTask;
import org.eclipse.birt.report.engine.api.ReportEngine;

/**
* @author lorenzm A formatted output document that can be viewed, printed,
* faxed, stored.
* @version $Revision: 1.1 $
*/
public abstract class Report {

/**
* A descriptive String set by my concrete subclasses at construction time.
*/
protected String name;
protected String format =PDF_FORMAT; //default PDF
public static final String PDF_FORMAT =HTMLRenderOption.OUTPUT_FORMAT_PDF;
public static final String HTML_FORMAT =HTMLRenderOption.OUTPUT_FORMAT_HTML;
protected static EngineConfig config;
protected static ReportEngine engine; //only create ONE engine for ALL reports

/**
* Constructor with required associations and/or state
*
* @pre name != null && name.length >0
* @param name
*/
public Report(String name) {
super();
this.name = name;
}

/**
* Set up for BIRT report generation. Subclasses will
* do the actual generation based on their own designs,
* but using the one and only engine I create.
* @return Returns the engine.
*/
protected ReportEngine getEngine() {
if( engine == null ) {
config = new EngineConfig( );
config.setEngineHome( "C:\\BIRT\\birt-runtime-2_0_0\\Report Engine" ); //TODO Set CORRECT home!!
// config.setLogConfig("C:\\BIRT\\logs\\", Level.FINER);
//only create ONE engine for ALL reports
engine = new ReportEngine( config );
// Create the emitter configuration.
HTMLEmitterConfig hc = new HTMLEmitterConfig( );
// Use the "HTML complete" image handler to write the files to disk.
HTMLCompleteImageHandler imageHandler = new HTMLCompleteImageHandler( );
hc.setImageHandler( imageHandler );
// Associate the configuration with the HTML output format.
config.setEmitterConfiguration( HTML_FORMAT, hc );
}
return engine;
}

/**
* Create a formatted output file using a BIRT .rptdesign XML file.
*/
public void generate() {
//------ Run reports
//--> IF params are needed for the report,
// add logic at http://eclipse.org/birt/index.php?page=deploy/engine.html

// Output can be HTML or PDF. PDF is default.
IReportRunnable report = null;
try {
System.out.println(getDesignFilepath()); //TODO remove for production
report = getEngine().openReportDesign( getDesignFilepath() );
} catch (EngineException e) {
e.printStackTrace();
return;
}
// Create a separate task for EACH report
IRunAndRenderTask task = getEngine().createRunAndRenderTask( report );
// Set up options.
HTMLRenderOption options = new HTMLRenderOption( );
// HTML (if specified) or PDF (default)
options.setOutputFormat( format );
String outputPath = "C:\\BIRT\\"; //TODO change to deployment location
outputPath += getOutputFilename();
options.setOutputFileName(outputPath);
task.setRenderOption( options );
HTMLRenderContext renderContext = new HTMLRenderContext();
renderContext.setImageDirectory( "./images" );
HashMap appContext = new HashMap();
appContext.put( EngineConstants.APPCONTEXT_HTML_RENDER_CONTEXT, renderContext );
task.setAppContext( appContext );
// Set parameter values using a HashMap. Parameters are name/value pairs.
// The values must be Java objects of the correct type.
HashMap params = getParameters(); //each report has its own params
task.setParameterValues( params );
task.validateParameters();

// Run the report.
try {
task.run( );
} catch (EngineException e) {
e.printStackTrace();
}
}

/**
* Only destroy the BIRT engine when exiting.
*/
public static void shutdown() {
engine.destroy( ); //cleanup
}

/**
* @return Parameter values to use
* Key = param name, Value = param value
*/
protected abstract HashMap getParameters();

/**
* @post $result != null
* @return The location of my BIRT .rptdesign file.
*/
protected abstract String getDesignFilepath();

/**
* @pre format == HTML_FORMAT || format == PDF_FORMAT
* @param format
* Must be HTML or PDF BIRT constant!
* @throws InvalidParameterException
* @throws Exception
*/
public void setFormat(String format) throws InvalidParameterException {
if( format.equals( PDF_FORMAT ) || format.equals( HTML_FORMAT ) ) {
this.format = format;
} else {
throw new InvalidParameterException("Invalid report format: " + format );
}
}
/**
* Return my name as the filename, with ".pdf" or ".html" as the extension
* based on my current format.
*
* @return String my name as the filename
*/
public String getOutputFilename() {
if( format.equals( PDF_FORMAT ) ) {
return getName() + ".pdf";
} else {
return getName() + ".html";
}
}

/**
* @return String
*/
public String getName() {
return name;
}

/**
* Method toString.
* @return String
*/
public String toString() {
return getName();
}
}


A concrete subclass would look like this:


package com.yourcompany.domain.reporting;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
* A Report of ...
* @author lorenzm
* @version $Revision: 1.1 $
*/
public class MyReport extends Report {
protected One one;
protected Two two;

/**
* Constructor with required parameters.
* @param one ...
* @param two ...
*/
public MyReport(One one, Two two) {
super("My Report");
this.one= one;
this.two= two;
}
/**
* Method getDesignFilepath.
* @return the complete path to my BIRT design file
*/
protected String getDesignFilepath() {
return "C:\\BIRT\\MyReport.rptdesign";
}
/**
* Method getParameters.
* @return my BIRT design input params
*/
protected HashMap getParameters() {
HashMap params = new HashMap();
params.put("one", one);
params.put("two", two);
return params;
}
/**
* Method toString.
* @return String
*/
public String toString() {
return "meaningful string, such as getName() + getParamString()"
;
}
}


That's it for now! Hope this has been beneficial.
Mark

Labels:


2 Comments:

  • Hi Mark,

    Nice Blog! I was wondering if you know a way of adding a group to a table dynamically i.e using the DE API and saving the report.Theres isnt any ocumentation available in this regard.

    Thanks.

    By Blogger Unknown, at January 24, 2007 12:21 PM  

  • Upgrade to Birt 2.1.1 to avail these features,even without the code script mentioned above
    Refer :
    http://www.eclipse.org/birt/phoenix/project/notable2.1.php

    By Blogger Suneesh, at February 13, 2007 6:57 AM  

Post a Comment

<< Home