Friday, November 20, 2009

Accessing Spring Beans from the BIRT Designer

Recently I have described methods that can be used to access Spring Beans from the BIRT Engine. These examples are intended to be illustrative and not comprehensive.

More on BIRT and Spring

Calling Spring Objects from BIRT Expressions and Event Handlers

In both of these examples I used the BIRT engine to retrieve Spring objects within the scripting environment. In this post I am supplying an example that illustrates how to implement your own menu in the expression builder, so Spring objects can be called within the BIRT Designer. This will allow you to test your report prior to deployment.

In this post:
BIRT 2.3.1 - Adding Functions to the Expression Builder
I described how to implement the org.eclipse.birt.core.ScriptFunctionService. The attached example is an implementation of this extension point. When using this example the expression builder will appear as follows.



Notice that there is now a SpringFunctions sub-category, which provides one method callBean. This method takes a bean name and a method name and expects a returned string. To keep the example simple no arguments are supported and the method must return a string. The plugin.xml looks like the following:



Provided with the example is a deployable J2EE application and the plugin that implements the ScriptFunctionService. See the readme for details on setting up the example. Also note that the deployable plugin is also attached in the example. You will need to add this to your designer and your runtime. This example works with both the Open Source BIRT designer and the Actuate BIRT designer.

Also included is an example report with the following output.


This example is available at Birt-Exchange.

Friday, November 13, 2009

It's Movember

Movember, that time of year when the days grow shorter, the weather grows colder and the family gathers today for the holidays.  Of course in my family the kids are looking at me and laughing, and I am getting 'that' look from Gretchen my wife.

Yes I am growing in a mustache in an effort to change the face of men's health.  To quote my team lead (I am a part of Team Fat Cyclist).

"Movember is the month formerly known as November. During this month, men — manly men — grow mos as a way to call attention to themselves (hey, I’m just being honest here). 
Then, when people ask you “Why are you growing a moustache?” — and they will ask you this question — you tell them about the cancers affecting men, and ask them to donate to your Mo donation page (the money will be channeled to the Prostate Cancer Foundation and to LiveStrong). 
So really, a mo is kinda like wearing a pink ribbon for breast cancer awareness. But a lot more personal. And harder to remove. And it’s displayed a lot higher."
-The Fat Cyclist (Elden Nelson)

If you are interested in supporting me and helping to raise money for men's health issues, please visit my mo page and donate.

If not me, please visit another great team, the Eclipse Momitters.

Thursday, November 12, 2009

More on BIRT and Spring

In my previous post I discussed calling Spring objects within a BIRT report. That example used an architecture similar to the simplified diagram below.



The Spring Context was injected in the BIRT app context and this gave the BIRT scripting environment access to the Spring Beans. This example could have been expanded further to the Open Source BIRT Viewer by adding a similar object to the Application Context for the BIRT Viewer. See this wiki page for more details. If you use the method described in the wiki page, you may prefer to implement this with a Spring RequestContextFilter.

The simplified architecture for this setup is presented below.



Another option is to use Spring Remoting and the Open Source BIRT Viewer tag libraries or standard URL integration. In this case you add the Spring jars to the WEB-INF/lib of the viewer and write a wrapper class for the Spring remote client. The wrapper class and Spring configuration file can then be added to the WEB-INF/lib directory of the Viewer to access your remote beans. The architecture for this setup would be as follows. Attached to this post is an example of this method.



If you are using the Actuate Java Components technology, you can use the JSAPI within a standard HTML page or generate it in a Spring View which will include the BIRT report. This approach allows Flash charts to be populated by Spring Beans and report interactivity like dynamically grouping, sorting and filtering all without re-executing the report.



The example, that is attached to this post, is a very simple Spring Remoting example. It contains two ANT projects, one for the server application and one that acts as the remote client wrapper. Once you download the example, build the server application and deploy it. Instructions are in a readme file. Then build the client wrapper jar and deploy it as described in the readme. The readme illustrates deploying to the Open Source BIRT Viewer or the Actuate Interactive Viewer. The example uses Spring HTTP Remoting, but any of the Spring Remoting technologies should work. An example report is provided for both deployed environments. Whether you are using the Open Source BIRT Report designer or the Actuate BIRT Designer, the expression to access the Spring Bean will be similar to the following.



Output for the example is as follows.

Open Source Viewer


Actuate Interactive Viewer


The Example is available at BIRT Exchange.

Thursday, November 05, 2009

Calling Spring Objects from BIRT Expressions and Event Handlers

Several examples are already available on the web, which demonstrate calling the BIRT engine from a Spring MVC application.

Integrating BIRT with Spring in a Web application
and
Eclipse BIRT in Spring web applications
are a couple of examples.

There is not much information on how to include Spring Beans within a report design. This post details an example of injecting the Spring ApplicationContext into BIRT’s AppContext object which will allow you to call your Spring Beans in BIRT expressions or event handlers. A link for the source is listed at the bottom. A readme file containing instructions for building the example is included in the download.

You can include the BIRT runtime by following the post above or examining the download. The Example implements a Spring Controller with the following code.


package org.eclipse.birt.spring;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.eclipse.birt.report.engine.api.HTMLRenderOption;
import org.eclipse.birt.report.engine.api.HTMLServerImageHandler;
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.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractController;

public class BirtController extends AbstractController {

private IReportEngine reportEngine;

protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {

String reportName = request.getParameter("ReportName");
ServletContext sc = request.getSession().getServletContext();
reportEngine = BirtEngine.getBirtEngine(sc);
IReportRunnable runnable = null;
runnable = reportEngine.openReportDesign( sc.getRealPath("/Reports")+"/"+reportName );
IRunAndRenderTask runAndRenderTask = reportEngine.createRunAndRenderTask(runnable);

HTMLRenderOption options = new HTMLRenderOption();
options.setOutputFormat("html");
options.setOutputStream(response.getOutputStream());
options.setImageHandler(new HTMLServerImageHandler());
options.setBaseImageURL(request.getContextPath()+"/images");
options.setImageDirectory(sc.getRealPath("/images"));
runAndRenderTask.setRenderOption(options);
runAndRenderTask.run();

return null;
}

}


Note that no error checking is implemented in this example. This controller class just extends the Spring AbstractController class passes the response object to the BIRT engine to output the report. The report name is retrieved from the request. Before running the report the BirtEngine class is used to retrieve the BIRT engine. This is virtually the same BirtEngine class used in the servlet example available in the BIRT wiki with some exceptions that are noted here.


package org.eclipse.birt.spring;

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

import org.eclipse.birt.report.engine.api.EngineConfig;
import org.eclipse.birt.report.engine.api.IReportEngine;
import javax.servlet.*;
import org.eclipse.birt.core.framework.PlatformServletContext;
import org.eclipse.birt.core.framework.IPlatformContext;
import org.eclipse.birt.core.framework.Platform;
import org.eclipse.birt.core.exception.BirtException;
import org.eclipse.birt.report.engine.api.IReportEngineFactory;
import org.springframework.context.ApplicationContext;

public class BirtEngine {

private static IReportEngine birtEngine = null;

private static Properties configProps = new Properties();

private final static String configFile = "BirtConfig.properties";

public static synchronized void initBirtConfig() {
loadEngineProps();
}
public static synchronized IReportEngine getBirtEngine(ServletContext sc) {
if (birtEngine == null)
{
//optionally load engine props
//loadEngineProps();
EngineConfig config = new EngineConfig();
if( configProps != null){
String logLevel = configProps.getProperty("logLevel");
Level level = Level.OFF;
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 ("OFF".equalsIgnoreCase(logLevel))
{
level = Level.OFF;
}

config.setLogConfig(configProps.getProperty("logDirectory"), level);
}
config.setEngineHome("");

IPlatformContext context = new PlatformServletContext( sc );
config.setPlatformContext( context );
config.getAppContext().put("PARENT_CLASSLOADER", BirtEngine.class.getClassLoader());
ApplicationContext sprCtx = ContextAccess.getApplicationContext();
config.getAppContext().put("spring",sprCtx);
try
{
Platform.startup( config );
}
catch ( BirtException e )
{
e.printStackTrace( );
}

IReportEngineFactory factory = (IReportEngineFactory) Platform
.createFactoryObject( IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY );
birtEngine = factory.createReportEngine( config );
}
return birtEngine;
}
public static synchronized void destroyBirtEngine() {
if (birtEngine == null) {
return;
}
birtEngine.destroy();
Platform.shutdown();
birtEngine = null;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
private static void loadEngineProps() {
try {
//Config File must be in classpath
ClassLoader cl = Thread.currentThread ().getContextClassLoader();
InputStream in = null;
in = cl.getResourceAsStream (configFile);
configProps.load(in);
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

This class just wraps access to the BIRT engine in a singleton.
The notable changes are in these lines of code deal with the EngineConfig class.

config.setEngineHome("");

IPlatformContext context = new PlatformServletContext( sc );
config.setPlatformContext( context );
config.getAppContext().put("PARENT_CLASSLOADER", BirtEngine.class.getClassLoader());
ApplicationContext sprCtx = ContextAccess.getApplicationContext();
config.getAppContext().put("spring",sprCtx);


The setEngineHome is passed a blank value and setting the PlatformContext to use a PlatformServletContext class will by default look for the BIRT plugins in the WEB-INF/Platform/plugins directory. Next we set the parent classloader for the report engine plugin so that classes available to the project will also be available to the BIRT engine. The final line gets the Spring ApplicationContext instance and loads it into the BIRT AppContext object and names it “spring”. The method used for retrieving the Spring context is described in a great post here.

The ContextAccess class is defined as follows.


package org.eclipse.birt.spring;
import org.springframework.context.ApplicationContext;
public class ContextAccess {
private static ApplicationContext ctx;
public static void setApplicationContext(ApplicationContext applicationContext) {
ctx = applicationContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}


This Class just contains methods to set and get the static variable ctx, which will contain the Spring ApplicationContext with the addition of one more class.



package org.eclipse.birt.spring;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;


public class SpringContextProvider implements ApplicationContextAware {
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
ContextAccess.setApplicationContext(ctx);
}
}



This Class implements the ApplicationContextAware interface which causes the Spring framework to callback this class with the Spring context, when it is created. The Spring configuration file for this example looks like the following.

<beans>
<bean id="springContextProvider" class="org.eclipse.birt.spring.SpringContextProvider">
</bean>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/runbirt.htm">birtController</prop>
</props>
</property>
</bean>
<bean id="birtEngine"
class="org.eclipse.birt.spring.BirtEngine"
destroy-method="destroyBirtEngine">
</bean>
<bean id="birtController"
class="org.eclipse.birt.spring.BirtController">
</bean>
<bean id="carPojo"
class="org.eclipse.birt.spring.CarPojo">
</bean>

</beans>

We have a carPojo bean that is available. To access this from a BIRT expression, all we have to do is use the following syntax.


var mypojo = spring.getBean("carPojo");


You can then call the methods on the object. Eg mypojo.getYear();



Output for the example is presented below.




Caveats
If you preview the report in the designer the report will not work. This is because the Spring context is not available to the designer. The report has to be deployed to test it. There are many ways this example could be extended to circumvent this issue. BIRT provides an extension point to enhance the expression builder which could be used with Spring Remoting to access the Spring context. BIRT also provides an extension to implement a BIRT application context object within the designer that could be used in this same fashion. I will try to implement one of these methods in the future to illustrate this concept.

The example can be downloaded from Birt-Exchange.