Building BIRT with Tycho

27. December, 2011

If you want to see BIRT built with Tycho, vote for this bug: [Build] Migrate build to Tycho


More Eclipse Projects Moving to Git

6. June, 2011

With Indigo, more Eclipse projects will move to Git.

Kudos go to the Git Task Force.

I’m especially happy that BIRT and the Eclipse platform is on the list.


Using Java in BIRT reports

12. November, 2010
BIRT Project

Image via Wikipedia

If you need to add complex operations to BIRT reports, you have several options. One that is often overlooked is to write the operation in Java and then use the Java code in the report. This is more simple than you’d think.

Instead of creating a “Report” project, create a Java project for your reports. Now you can put the Java code in the same project or a different project and add that second project to the list of dependencies of your report project. Note that this only works if the report project is of type “Java”.

When you edit your code, you just need to save and run your report (using the various ways to run the preview).

You can even debug the code. There is just one thing you need to be aware of: In the debug configuration, you can specify if you want to debug Java, JavaScript of both. JavaScript is the default. In this mode, Java breakpoints have no effect.


Error creating array in JavaScript

6. October, 2010

If you see this error in JavaScript (for example in a BIRT report):

org.mozilla.javascript.EvaluatorException: error instantiating (): class java.sql.Array is interface or abstract

then don’t include the package java.sql (as in importPackage(Packages.java.sql);) because if you do, new Array() will find the SQL class first and it will fail.


Extremely complex queries in BIRT reports

30. September, 2010

Sometimes, SQL just isn’t enough. What do you do when you just can’t get the SQL to run in under an hour while a little piece of Java code does the same query in a few seconds? Put the result into a report table and then run the report against that table.


Debugging BIRT: How do I enable logging for OSGi/Equinox?

12. July, 2010

If you ever tried to enable logging for OSGi (Equinox) because starting the BIRT engine fails for mysterious reasons, you will have noticed that BIRT removes all osgi.* options from the System.properties before it launches (see ).

Instead, it expects these options in config.ini (which must be in the current folder):

# Specify the file with the debug options. See the .options file in the org.eclipse.osgi*.jar for examples
osgi.debug=/path/to/file/with/debug.options
# Change the classloader. Possible values are: "app", "fwk", "boot" (default)
# app: Use the current SystemClassLoader
# boot: Use the boot classloader
# fwk: Use the classloader which was used to load OSGi.
#osgi.parentClassloader=fwk

Use fwk if you see errors because of missing XML parser classes. The Java runtime has a private static field which contains the XML parser factory and if you touch any XML code before you start OSGi, then that field will be set and OSGi will be forced to use this XML parser — only the default boot classloader can’t see the parser. Bummer.


Testing BIRT

23. July, 2007

I’m a huge fan of TDD. Recently, I had to write tests for BIRT, specifically for a bug we’ve stumbled upon in BIRT 2.1 that has been fixed in 2.2: Page breaks in tables.

The first step was to setup BIRT so I can run it from my tests.

public IReportEngine getEngine () throws BirtException
{
    EngineConfig config = new EngineConfig();
    config.setLogConfig("/tmp/birt-log", Level.FINEST);
    
    // Path to the directory which contains "platform"
    config.setEngineHome(".../src/main/webapp");
    PlatformConfig pc = new PlatformConfig ();
    pc.setBIRTHome(basepath);
    PlatformFileContext context = new PlatformFileContext(pc);
    config.setPlatformContext(context);
    
    Platform.startup(config);
    
    IReportEngineFactory factory = (IReportEngineFactory) Platform
    .createFactoryObject(IReportEngineFactory
        .EXTENSION_REPORT_ENGINE_FACTORY);
    if (factory == null)
 throw new RuntimeException ("Couldn't create factory");
    
    return factory.createReportEngine(config);
}

My main problems here: Find all the parts necessary to install BIRT, copy them to the right places and find out how to setup EngineConfig (especially the platform part).

public void renderPDF (OutputStream out, File reportDir,
        String reportFile, Map reportParam) throws EngineException
{
    File f = new File (reportDir, reportFile);
    final IReportRunnable design = birtReportEngine
        .openReportDesign(f.getAbsolutePath());
    //create task to run and render report
    final IRunAndRenderTask task = birtReportEngine
        .createRunAndRenderTask(design);
    
    // Set parameters for report
    task.setParameterValues(reportParam);
    
    //set output options
    final HTMLRenderOption options = new HTMLRenderOption();
    options.setOutputFormat(HTMLRenderOption.OUTPUT_FORMAT_PDF);
    options.setOutputStream(out);
    task.setRenderOption(options);
        
    //run report
    task.run();
    task.close();
}

I’m using HTMLRenderOption here so I could use the same code to generate HTML and PDF.

In my test case, I just write the output to a file:

public void testPageBreak () throws Exception
{
    Map params = new HashMap (20);
    ...
    
    File dir = new File ("tmp");
    if (!dir.exists()) dir.mkdirs();
    File f = new File (dir, "pagebreak.pdf");
    if (f.exists())
    {
 if (!f.delete())
     fail ("Can't delete "+f.getAbsolutePath()
         + "nMaybe it's locked by AcrobatReader?");
    }
    
    FileOutputStream out = new FileOutputStream (f);
    ReportGenerator gen = new ReportGenerator();
    File basePath = new File ("../webapp/src/main/webapp/reports");
    gen.generateToStream(out, basePath, "sewingAtelier.rptdesign"
        , params);
    if (!f.exists())
 fail ("File wasn't written. Please check the BIRT logfile!");
}

Now, this is no test. It’s only a test when it can verify that the output is correct. To do this, I use PDFBox:

    PDDocument doc = PDDocument.load(new File ("tmp", "pagebreak.pdf"));
    // Check number of pages
    assertEquals (6, doc.getPageCount());
    assertEquals ("Error on page 1",
            "...n" + 
            "...n" +
     ...
            "..."
            , getText (doc, 1));

The meat is in getText():

private String getText (PDDocument doc, int page) throws IOException
{
    PDFTextStripper textStripper = new PDFTextStripper ();
    textStripper.setStartPage(page);
    textStripper.setEndPage(page);
    String s = textStripper.getText(doc).trim();
    
    Pattern DATE_TIME_PATTERN = Pattern.compile("^dd.dd.dddd dd:dd Page (d+) of (d+)$", Pattern.MULTILINE);
    Matcher m = DATE_TIME_PATTERN.matcher(s);
    s = m.replaceAll("23.07.2007 14:02 Page $1 of $2");
    
    return fixCRLF (s);
}

I’m using several tricks here: I’m replacing a date/time string with a constant, I stabilize line ends (fixCRLF() contains String.replaceAll("rn", "n");) and do this page by page to check the whole document.

Of course, since getText() just returns the text of a page as a String, you can use all the other operations to check that everything is where or as it should be.

Note that I’m using MockEJB and JNDI to hand a datasource to BIRT. The DB itself is Derby running in embedded mode. This allows me to connect to directly a Derby 10.2 database even though BIRT comes with Derby 10.1 (and saves me the hazzle to fix the classpath which OSGi builds for BIRT).

@Override
protected void setUp () throws Exception
{
    super.setUp();
    MockContextFactory.setAsInitial();
    
    Context ctx = new InitialContext();
    MockContextFactory.setDelegateContext(ctx);
    
    EmbeddedDataSource ds = new EmbeddedDataSource ();
    ds.setDatabaseName("tmp/test_db/TestDB");
    ds.setUser("");
    ds.setPassword("");

    ctx.bind("java:comp/env/jdbc/DB", ds);
}

@Override
protected void tearDown () throws Exception
{
    super.tearDown();
    MockContextFactory.revertSetAsInitial();
}

Links: