Java, Open Source

BIRT/Stripes example

A long time has passed since my previous BIRT examples Deploying BIRT Report Engine API with Jakarta Struts and Deploying BIRT Report Engine API with Stripes.
Although they have received a lot of attention and downloads, the examples were really basic and are now outdated.

Since I had to use BIRT in my last projects I decided to update my BIRT/Stripes example.
This example doesn’t depend on the Tribix project anymore since BIRT supports HTML/Paginated HTML, PDF, Excel, Word, PowerPoint, and PostScript outputs (images/charts are not embedded in Excel output).

BIRT WebReport Example

I’ve tried to follow some good practices that I think are important to use in a production application:

  • There is a significant cost associated with creating an engine instance, due primarily to the cost of loading extensions. Therefore, each application should create just one ReportEngine instance and use it to run multiple reports. In this example the engine is started in the context listener and the same instance is always used.
  • All texts in the report should be loaded from the resources so the application can be fully localizable and fully internationalized.
  • You should use a JDBC data set to preview your report with BIRT designer but you must swap the data set in runtime to use data from your business logic.
  • You should use predefined styles instead of custom styles as much as you can.
  • Not a good practice but often a requirement, hide the master page when generating a HTML report, and change the visibility of elements so they are visible only to specified outputs.

For this example BIRT Runtime Engine 2.2.2 2.3.1, and Stripes Framework 1.5 were used.

Integrating BIRT Report Engine with your application is as easy as before:

  1. Copy all the jars inside the birt-runtime-2_3_1/ReportEngine/lib folder from the Report Engine download into /WEB-INF/lib.
  2. Next, create a folder named platform inside /WEB-INF.
  3. Copy birt-runtime-2_3_1/Report Engine/plugins and birt-runtime-2_3_1/ReportEngine/configuration folders into the folder /WEB-INF/platform that you’ve just created.

BirtEngine.java
BIRT Report Engine configuration.


/*
 * $Id$
 *
 * Copyright 2008 samaxes.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.samaxes.webreport.presentation;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.logging.Level;

import javax.servlet.ServletContext;

import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.core.framework.IPlatformContext;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.core.framework.PlatformServletContext;
import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.IReportEngine;
import org.eclipse.birt.report.engine.api.IReportEngineFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * BIRT Report Engine configuration.
 *
 * @author Samuel Santos
 * @version $Revision$
 */
public class BirtEngine {

    private static final Logger logger = LoggerFactory.getLogger(BirtEngine.class);

    private static IReportEngine birtEngine = null;

    private static Level level = Level.OFF;

    private static String logDirectory = null;

    /**
     * Gets the BIRT Report Engine.
     *
     * @return the BIRT Report Engine
     */
    public static IReportEngine getBirtEngine() {
        return birtEngine;
    }

    /**
     * Initialize BIRT Report Engine configuration.
     *
     * @param servletContext the ServletContext
     */
    public static synchronized void initBirtEngine(ServletContext servletContext) {
        if (birtEngine == null) {
            loadEngineProps();

            IPlatformContext context = new PlatformServletContext(servletContext);
            EngineConfig config = new EngineConfig();

            config.setLogConfig(logDirectory, level);
            config.setEngineHome("");
            config.setPlatformContext(context);
            config.setResourcePath(BirtEngine.class.getResource("/").getPath());

            try {
                Platform.startup(config);
            } catch (BirtException e) {
                logger.error(e.getMessage(), e);
            }

            IReportEngineFactory factory = (IReportEngineFactory) Platform
                    .createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
            birtEngine = factory.createReportEngine(config);
            birtEngine.changeLogLevel(Level.WARNING);
        }
    }

    /**
     * Destroys the BIRT Report Engine.
     */
    public static synchronized void destroyBirtEngine() {
        if (birtEngine != null) {
            birtEngine.destroy();
        }
        Platform.shutdown();
    }

    /**
     * Creates and returns a copy of this object.
     *
     * @return a clone of this instance.
     * @exception CloneNotSupportedException if the object's class does not support the <code>Cloneable</code>
     *                interface. Subclasses that override the <code>clone</code> method can also throw this exception
     *                to indicate that an instance cannot be cloned.
     */
    public Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }

    /**
     * Loads the Engone properties.
     */
    private static void loadEngineProps() {
        try {
            // Config File must be in classpath
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Properties configProps = new Properties();
            InputStream in = null;

            in = cl.getResourceAsStream("BirtConfig.properties");
            configProps.load(in);
            in.close();

            String logLevel = configProps.getProperty("logLevel");
            if ("SEVERE".equalsIgnoreCase(logLevel)) {
                level = Level.SEVERE;
            } else if ("WARNING".equalsIgnoreCase(logLevel)) {
                level = Level.WARNING;
            } else if ("INFO".equalsIgnoreCase(logLevel)) {
                level = Level.INFO;
            } else if ("CONFIG".equalsIgnoreCase(logLevel)) {
                level = Level.CONFIG;
            } else if ("FINE".equalsIgnoreCase(logLevel)) {
                level = Level.FINE;
            } else if ("FINER".equalsIgnoreCase(logLevel)) {
                level = Level.FINER;
            } else if ("FINEST".equalsIgnoreCase(logLevel)) {
                level = Level.FINEST;
            } else if ("ALL".equalsIgnoreCase(logLevel)) {
                level = Level.ALL;
            }
            logDirectory = configProps.getProperty("logDirectory");
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
}

WebReportActionBean.java
Actions related to the reports generation.


/*
* $Id$
*
* Copyright 2008 samaxes.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.samaxes.webreport.presentation.action;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.action.StreamingResolution;

import org.eclipse.birt.report.engine.api.EngineConstants;
import org.eclipse.birt.report.engine.api.EngineException;
import org.eclipse.birt.report.engine.api.HTMLRenderOption;
import org.eclipse.birt.report.engine.api.HTMLServerImageHandler;
import org.eclipse.birt.report.engine.api.IHTMLRenderOption;
import org.eclipse.birt.report.engine.api.IPDFRenderOption;
import org.eclipse.birt.report.engine.api.IRenderOption;
import org.eclipse.birt.report.engine.api.IReportEngine;
import org.eclipse.birt.report.engine.api.IReportRunnable;
import org.eclipse.birt.report.engine.api.IRunAndRenderTask;
import org.eclipse.birt.report.engine.api.PDFRenderOption;
import org.eclipse.birt.report.engine.api.RenderOption;
import org.eclipse.birt.report.model.api.DesignElementHandle;
import org.eclipse.birt.report.model.api.activity.SemanticException;

import com.samaxes.webreport.entities.WebReportEntity;
import com.samaxes.webreport.presentation.BirtEngine;

/**
* Actions related to the reports generation.
*
* @author Samuel Santos
* @version $Revision$
*/
public class WebReportActionBean implements ActionBean {

   private ActionBeanContext context;

   public ActionBeanContext getContext() {
       return context;
   }

   public void setContext(ActionBeanContext context) {
       this.context = context;
   }

   /**
    * Generates the html report.
    *
    * @return forward to the jsp page
    * @throws EngineException when opening report design or reunning the report
    * @throws SemanticException when changing properties of DesignElementHandle
    */
   @DefaultHandler
   public Resolution htmlReport() throws EngineException, SemanticException {
       ByteArrayOutputStream reportOutput = new ByteArrayOutputStream();

       // set output options
       IHTMLRenderOption options = new HTMLRenderOption();
       options.setOutputFormat(HTMLRenderOption.OUTPUT_FORMAT_HTML);
       options.setOutputStream(reportOutput);
       options.setEmbeddable(true);
       options.setBaseImageURL(context.getRequest().getContextPath() + "/images");
       options.setImageDirectory(context.getServletContext().getRealPath("/images"));
       options.setImageHandler(new HTMLServerImageHandler());
       options.setMasterPageContent(false);

       generateReport(options);

       return new StreamingResolution("text/html", new ByteArrayInputStream(reportOutput.toByteArray()));
   }

   /**
    * Generates the PDF report.
    *
    * @return PDF file with the report output
    * @throws EngineException when opening report design or reunning the report
    * @throws SemanticException when changing properties of DesignElementHandle
    */
   public Resolution pdfReport() throws EngineException, SemanticException {
       ByteArrayOutputStream reportOutput = new ByteArrayOutputStream();

       // set output options
       IPDFRenderOption options = new PDFRenderOption();
       options.setOutputFormat(PDFRenderOption.OUTPUT_FORMAT_PDF);
       options.setOutputStream(reportOutput);

       generateReport(options);

       return new StreamingResolution("application/pdf", new ByteArrayInputStream(reportOutput.toByteArray()))
               .setFilename("WebReport.pdf");
   }

   /**
    * Generates the Excel report.
    *
    * @return Excel file with the report output
    * @throws EngineException when opening report design or reunning the report
    * @throws SemanticException when changing properties of DesignElementHandle
    */
   public Resolution xlsReport() throws EngineException, SemanticException {
       ByteArrayOutputStream reportOutput = new ByteArrayOutputStream();

       // set output options
       IRenderOption options = new RenderOption();
       options.setOutputFormat("xls");
       options.setOutputStream(reportOutput);

       generateReport(options);

       return new StreamingResolution("application/vnd.ms-excel", new ByteArrayInputStream(reportOutput.toByteArray()))
               .setFilename("WebReport.xls");
   }

   /**
    * Generates the Word report.
    *
    * @return Excel file with the report output
    * @throws EngineException when opening report design or reunning the report
    * @throws SemanticException when changing properties of DesignElementHandle
    */
   public Resolution docReport() throws EngineException, SemanticException {
       ByteArrayOutputStream reportOutput = new ByteArrayOutputStream();

       // set output options
       IRenderOption options = new RenderOption();
       options.setOutputFormat("doc");
       options.setOutputStream(reportOutput);

       generateReport(options);

       return new StreamingResolution("application/vnd.ms-word", new ByteArrayInputStream(reportOutput.toByteArray()))
               .setFilename("WebReport.doc");
   }

   /**
    * Generates the Powerpoint report.
    *
    * @return Excel file with the report output
    * @throws EngineException when opening report design or reunning the report
    * @throws SemanticException when changing properties of DesignElementHandle
    */
   public Resolution pptReport() throws EngineException, SemanticException {
       ByteArrayOutputStream reportOutput = new ByteArrayOutputStream();

       // set output options
       IRenderOption options = new RenderOption();
       options.setOutputFormat("ppt");
       options.setOutputStream(reportOutput);

       generateReport(options);

       return new StreamingResolution("application/vnd.ms-powerpoint", new ByteArrayInputStream(reportOutput
               .toByteArray())).setFilename("WebReport.ppt");
   }

   /**
    * Generates the Postscript report.
    *
    * @return Excel file with the report output
    * @throws EngineException when opening report design or reunning the report
    * @throws SemanticException when changing properties of DesignElementHandle
    */
   public Resolution psReport() throws EngineException, SemanticException {
       ByteArrayOutputStream reportOutput = new ByteArrayOutputStream();

       // set output options
       IRenderOption options = new RenderOption();
       options.setOutputFormat("postscript");
       options.setOutputStream(reportOutput);

       generateReport(options);

       return new StreamingResolution("application/postscript", new ByteArrayInputStream(reportOutput.toByteArray()))
               .setFilename("WebReport.ps");
   }

   /**
    * Generates the report output.
    *
    * @return the report output
    * @throws EngineException when opening report design or reunning the report
    * @throws SemanticException when changing properties of DesignElementHandle
    */
   private void generateReport(IRenderOption options) throws EngineException, SemanticException {
       // this list simulates a call to the application business logic
       List<WebReportEntity> webReportEntities = new ArrayList<WebReportEntity>();
       webReportEntities.add(new WebReportEntity(new Double(2), "Product1"));
       webReportEntities.add(new WebReportEntity(new Double(4), "Product2"));
       webReportEntities.add(new WebReportEntity(new Double(7), "Product3"));

       // get the engine
       IReportEngine birtReportEngine = BirtEngine.getBirtEngine();

       // open the report design
       IReportRunnable design = birtReportEngine.openReportDesign(context.getServletContext().getRealPath("/reports")
               + "/webReport.rptdesign");

       // create task to run and render report
       IRunAndRenderTask task = birtReportEngine.createRunAndRenderTask(design);
       @SuppressWarnings("unchecked")
       Map<String, Object> appContext = task.getAppContext();
       DesignElementHandle reportChart = task.getReportRunnable().getDesignHandle().getModuleHandle().findElement(
               "reportChart");

       appContext.put(EngineConstants.APPCONTEXT_CLASSLOADER_KEY, WebReportActionBean.class.getClassLoader());
       appContext.put("webReportEntities", webReportEntities);
       reportChart.setProperty("dataSet", "Scripted Data Set"); // change the chart dataset

       task.setLocale(context.getLocale());
       task.setRenderOption(options);
       task.setAppContext(appContext);

       // run report
       task.run();
       task.close();
   }
}

If you checkout the source code from the source code repository you should rename build.properties-sample to build.properties and BirtConfig.properties-sample to BirtConfig.properties and edit them to reflect your environment.

Resources

Related Posts

6 Comments

speak up

Add your comment below, or trackback from your own site.

Subscribe to these comments.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

*Required Fields