Wednesday, March 04, 2009

Raising Errors in Event Handlers

Let me start with a simple scenario.  You are writing a BIRT event handler and you expect the user to set a UserDefinedProperty that will guide the event handler code.  How do you handle the situation where the user forgets to add the UserDefinedVariable?  How do you provide a message back to the user to help them solve the problem?

In the past I have focused on using logging and stepping through code.  While effective, it requires the person using your event handler code to have a relatively high level of sophistication.  Wouldn't it be great if you could pass an error message from your event handler code to the ReportEngine, so that the message would be displayed through the standard error handling mechanism?

The methods that are used to add exceptions to a report are not visible from the Script API.  I hope that we can get an enhancement into BIRT 2.5 that will make this easy to do, but what if you want to add code like this to the early version of BIRT.

It turns out that it is easy enough to do using Java reflection.  The ReportContext object wraps an instance of the ExecutionContext.  ExecutionContext supports a method:
     addException(BirtException exception)


All we need to do is figure out how to expose this method to our EventHandlers (Java or JavaScript).  To do this I created a static method called addBirtException, the complete code listing is as follows:

public static void addBirtException(IReportContext reportContext , String errorMessage) {
    addBirtException(reportContext , errorMessage, BirtException.WARNING);
}

public static void addBirtException(IReportContext reportContext , String errorMessage, Integer severity) {
    
    BirtException be = new BirtException("org.eclipse.birt.report.engine", errorMessage, new Object[]{""} );
    be.setSeverity(severity);

    try {
        // get the protect field 'context' from reportContext
        Class rciClass = reportContext.getClass();
        Field fieldFromScript = rciClass.getDeclaredField("context");
        if (fieldFromScript == null) {
            return;
        }

        // instantiate the ExecutionContext object that
        // populates the context field
        fieldFromScript.setAccessible(true);
        Object execContext = fieldFromScript.get(reportContext);

        // now get a handle to the addException method on ExecutionObject
        Class execClass = execContext.getClass();
        Method addMethod = execClass.getMethod("addException", new Class[] { BirtException.class });

        // finally invoke the method which will add the BirtException 
        // to the report
        addMethod.setAccessible(true);
        addMethod.invoke(execContext, new Object[] { be });

        // Lots of ways for this to break...
    } catch (Exception e) {
        logger.warning(e.getMessage());
        e.printStackTrace();
    } 
    return;
}


Once you have the ScriptUtil class in your classpath you can call it from your Java class by  adding this code:

    ScriptUtil.addBirtException(reportContext, "Simple Error Message", BirtException.WARNING);

Will cause your report to run with the following error messages to display in your report.



But why stop there.  Now that you have the ability you can go ahead and provide detailed help back to the people that are using your report or event handler class:

    ScriptUtil.addBirtException(reportContext, "Simple Error Message", BirtException.WARNING);

    StringBuffer sb = new StringBuffer();
    sb.append("Complex error message that is designed to help a\n");
    sb.append("report developer that is using your event handler\n");
    sb.append("to successfully debug an improper implementation.\n");
    sb.append("Could also be used to help end users identify report\n");
    sb.append("issues to support and help desk staff.\n");
    sb.append("\t - Can include error codes from other products\n");
    sb.append("\t - Can include domain specific messages.\n\n");
    sb.append("No more looking around for message logs or tracing through\n");
    sb.append("gnarly stack traces");
    ScriptUtil.addBirtException(reportContext, sb.toString(), BirtException.WARNING);

gives you:


This code is also easily callable from JavaScript.

Packages.deapi_event_handler.ScriptUtil.addBirtException(addBirtException, "my message");

So pretty cool eh?

But there is more, the BirtException supports localization, resource bundles, formatting all of the features that you may want in advanced report development.

5 comments:

Anonymous said...

Hi Scott, could you attach a sample .rptdesign file for this post? Where should the addBirtException method be placed ? How does it relate to ScriptUtil ? I want to redirect user to a common error page when an error occur. Could this be done ?

Anonymous said...

Thanks for reading and asking a question.

I am under the gun on a number of deliverable items this week, so I need to be brief.

Basically, you can call addBirtException anywhere you have code. Expressions, JavaScript event handlers, and Java event handlers.

In terms of re-directing to a common error page, I need a bit more info about what you have in mind, and how you are running your reports.

Anonymous said...

Hi Scott,

Thanks a lot for this information.
I created a custom scriptlet class as given above and then tested a report template with the following in the initialize event of the report:

ScriptlUtils.addBirtException(reportContext, "Simple Error Message - Hello World2!", BirtException.ERROR);

I then tested this using the BIRT Viewer installed on tomcat and got the expected exception after running the report - so all worked fine as you described.

I then tried running the same report via a batch file (which contains a java command to call the the ReportRunner class and pass report parameters to it).
However, the exception did not stop the report from running at all and an error status of 0 was returned to the batch file on completion of the java command.

I would have expected the error level to be propagated back to the batch file.

i.e.
BirtException.OK: should return 0 to the batch file
BirtException.INFO: should return 1 to the batch file
BirtException.WARNING: should return 2 to the batch file
BirtException.ERROR: should return 4 to the batch file
BirtException.CANCEL: should return 8 to the batch file

Is there some way to get the correct error level in the batch file?

Anonymous said...

Hello Scott,

Is it possible to remove the text: "The following items have errors: ReportDesign(id=1):" from the reporting of the report exception?

Thanks.

Anonymous said...

Is this still the right/only way of presenting errors to the client?
In the post it mentions an enhancement in BIRT 2.5 that would make the process simpler, was this ever done?