Saturday, January 3, 2009

Crystal Reports for Eclipse - Ensuring Report Cleanup

I work for SAP Business Objects in Technical Customer Assurance. My speciality is the Software Development Kits (SDKs) that we provide with our Business Intelligence products - BusinessObjects Enterprise, Web Intelligence, Desktop Intelligence, Crystal Reports and Crystal Xcelsius.

In my blog, I discuss subjects that I personally find interesting - little known or not-well-documented corners of the SDK, new functionality or new SDKs, or interesting issues that I've come across in a SAP Incident or SAP Developer Network forums.

You're more than welcome to suggest any topic (SAP Business Objects SDK related, of course...) that you'd like me to discuss - I have a dozen or so items on my blog to-do list, but I'm always on the hunt for anything interesting with our SDKs.

If you're a developer using Crystal Reports for Eclipse (CR4E) for your reporting component, this blog entry will be of interest to you. I'll discuss how to ensure proper cleanup of reports processed by the CR4E SDK - the Java Reporting Component and Crystal Reports Java.

This isn't covered in detail in the documentation, but cleanup with CR4E SDKs is particularly important if your application comes under load. Reports left open take up resources, which, if left unchecked, can starve your app of resources, leading to performance degradation, report request denial, and out of memory problems.

Relationship to the Report Application Server SDK

Underlying the CR4E reporting SDKs are a common root originating from the Report Application Server (RAS) SDK. The RAS solution is server-client, where the RAS SDK communicates with the RAS server via the Enterprise Framework CORBA TCP/IP connection. For the CR4E SDKs, the server-client communication is replaced by an in-process connection to a pure 100% Java reporting engine.

One carryover of the RAS framework in the CR4E API is how the lifetime of a opened report instance is controlled. From the perspective of a programmer coding against the API, the engine appears to use reference counting in determine whether a report instance is still in use or is free to be cleaned up.[1]

Controlling Report Instance Lifetime

In a nutshell, follow these rules to ensure proper cleanup:

  1. Close every ReportClientDocument.
  2. Destroy every ReportSource.

Every time a ReportClientDocument opens a report rpt file, the reference count for that report instance is incremented. Every time an unique ReportSource is retrieved from the ReportClientDocument, the count is incremented.

Every time a ReportClientDocument is closed, the reference count is decremented. Every time a ReportSource is destroyed, the count is decremented.

Once the reference count goes to zero, the in-process report engine releases all resources used by the instance. If ReportClientDocument or ReportSource is not cleaned up, then the instance will remain live and taking up resources until timeout garbage collects the object.

One important consideration is the following: even if you invoke ReportClientDocument.close(), that report may still not be closed.

Here's a simple test: (1) open a report with a ReportClientDocument, (2) retrieve a ReportSource using the method ReportClientDocument.getReportSource(), (3) invoke the ReportClientDocument.close() method for that instance, (4) test the value of ReportClientDocument.isOpen() - you'll see that it's true!, (5) pass the ReportSource object to the CrystalReportViewer, and invoke CrystalReportViewer.dispose(), (6) now check ReportClientDocument.isOpen() again - you'll see that it's false, i.e., that the instance has been cleaned up.

Another consideration: the only way to dispose the ReportSource is to pass it to a viewer object - CrystalReportViewer or ReportExportControl - an invoke dispose() on the viewer, since the ReportSource API is not public. Invoking viewer dispose() will in turn invoke the proper dispose method for the ReportSource.

Yet another consideration: the CrystalReportViewer DHTML web viewer works by post-back on client user events. This means you'd keep either ReportClientDocument or ReportSource instance in HTTP Session context and, on each postback, retrieve the object and inject the ReportSource into the CrystalReportViewer. Whether you'd dispose the CrystalReportViewer depends on which object you keep in Session.

Sample Codes

I'll illustrate using following sample codes for viewing reports on the web, where I keep the ReportSource in session. The lifecycle consists of three parts (1) opening the report, (2) handling viewer postback, and (3) final cleanup.

JSP Page to Open Report and Handle Postback

The above handles opening the report and viewer postback. Postback is detected by checking to see if the request parameter "CrystalEventTarget" is set to the name property specified for the CrystalReportViewer.

If the request is an initial request, the ReportClientDocument is used to open the report (reference count = 1), generate a ReportSource (reference count = 2), store the ReportSource in HTTP Session, then close the ReportClientDocument (reference count = 1). Even though the ReportClientDocument instance is closed, the reference count is non-zero, so the report instance remains open.

If the request is a postback, then the ReportSource is retrieved from HTTP Session.

Whether the request is postback or not, the ReportSource is passed to the viewer and processHttpRequest called to write the report page to the web browser. Note that the viewer dispose() method is not called in this page, since we wish to keep the report open for subsequent viewer postback requests, and do not want to dispose the ReportSource kept in HTTP Session.

JSP Page for Final Report Cleanup

The JSP page above is used for final cleanup - it retrieves the ReportSource from HTTP Session, passes it to the CrystalReportViewer and invokes dispose(), to zero the reference count and release all resources held by the report. Note that the CrystalReportViewer instance here is not used for any viewing purpose - it's instantiated solely to dispose the ReportSource.

This page should be call only when the user is done viewing the report. There's several ways to invoke this, for example (1) have this page called when a new report is requested, to clean up any previously viewed reports, or (2) create a 'Logoff' button, that the user clicks that posts to this page, or (3) wrap the viewer in a Frame, or create a hidden Frame, that on page unload events invokes JavaScript that calls this page, or (4) put the code in a HTTP Session Listener instance registered with the web application to listen for HTTP Session timeout events.


The workflow required for proper report lifecycle management isn't very intuitive, unless you know a bit about the internal workings of the Java Crystal Report Engine.

I hope you find this information useful, and helpful in creating a more performant CR4E reporting application.


[1] Why RAS appear to use reference counting arises from a change in XI Release 2, when Serialization support was introduced. Since the RAS SDK reference to a report on the RAS server may cross across multiple web application servers, the server keeps track of the number of oustanding references extant. Once the count goes to zero, the server assumes the report instance is no longer being used, and cleans up resources used by the instance. The CR4E SDKs do not, at this time, support Serialization of report objects, but still uses this behavior to determine report instance lifetime.

1 comment:

  1. Not if I can help on the following error describe the problem. Create a Java application that opens a rpt when JBOSS amount and Windows is working properly, but when the amount in Red Hat JBOSS and I get the following error could support me to tell me if I need to set something in linux.
    [http-/] 11/11/15-15:52:12 detected an exception:
    at com.businessobjects.reports.sdk.builder.EROMReportDocumentBuilder.char(SourceFile:259)
    at com.businessobjects.reports.sdk.builder.EROMReportDocumentBuilder.try(SourceFile:132)
    at com.businessobjects.reports.sdk.JRCCommunicationAdapter.if(SourceFile:660)
    at com.businessobjects.reports.sdk.JRCCommunicationAdapter.a(SourceFile:166)
    at com.businessobjects.reports.sdk.JRCCommunicationAdapter$2.a(SourceFile:528)
    at com.businessobjects.reports.sdk.JRCCommunicationAdapter$
    at com.crystaldecisions.reports.common.ThreadGuard.syncExecute(SourceFile:102)
    at com.businessobjects.reports.sdk.JRCCommunicationAdapter.for(SourceFile:524)
    at com.businessobjects.reports.sdk.JRCCommunicationAdapter.request(SourceFile:351)
    at com.businessobjects.sdk.erom.jrc.a.a(SourceFile:54)
    at com.businessobjects.sdk.erom.jrc.a.execute(SourceFile:67)
    at com.crystaldecisions.proxy.remoteagent.RemoteAgent$a.execute(SourceFile:716)
    at com.crystaldecisions.proxy.remoteagent.CommunicationChannel.a(SourceFile:125)
    at com.crystaldecisions.proxy.remoteagent.RemoteAgent.a(SourceFile:537)
    at javax.servlet.http.HttpServlet.service(
    at javax.servlet.http.HttpServlet.service(
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(
    at org.apache.catalina.core.StandardWrapperValve.invoke(
    at org.apache.catalina.core.StandardContextValve.invoke(
    at org.apache.catalina.core.StandardHostValve.invoke(
    at org.apache.catalina.valves.ErrorReportValve.invoke(
    at org.apache.catalina.core.StandardEngineValve.invoke(
    at org.apache.catalina.connector.CoyoteAdapter.service(
    at org.apache.coyote.http11.Http11Processor.process(
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(

    [http-/] 11/11/15-15:52:12 Ocurriò un error: