<p>+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. </p><p>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. </p><p>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.+ </p>
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.
h5. Relationship to the Report Application Server SDKUnderlying 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] | #reference_counting]
h5. Controlling Report Instance LifetimeIn a nutshell, follow these rules to ensure proper cleanup:
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.
h5. Sample CodesI'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.
bq. JSP Page to Open Report and Handle Postback<br /><textarea cols="75" rows="50"><%@ pageThe 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.
bq. JSP Page for Final Report Cleanup<br /><textarea cols="75" rows="15"><%@ page import="com.crystaldecisions.report.web.viewer.CrystalReportViewer"