Friday, 16 September 2011

Using the Crystal Reports Java API to generate PDF

I recently had to investigate how to generate a PDF from a Crystal Report created by another team. Without knowing anything about Crystal Reports, I had to google around for information and piece it all together. It turns out to be really simple once you know how.

We were using Business Objects 4.0, and probably the most important thing was to get the Java library - download 'SAP Crystal Reports for Java runtime components - Java Reporting Component (JRC)' from
http://www.businessobjects.com/campaigns/forms/downloads/crystal/eclipse/datasave.asp

There are a lot of samples on the web to look at - you might find something to help here:
http://wiki.sdn.sap.com/wiki/display/BOBJ/Java+Reporting+Component++SDK+Samples

A good example to start with is “JRC EXPORT REPORT”:
http://www.sdn.sap.com/irj/scn/go/portal/prtroot/docs/library/uuid/40d580ce-bd66-2b10-95b0-cc4d3f2dcaef

If you have an RPT file, the java to generate the report is relatively simple - :

ReportClientDocument reportClientDoc = new ReportClientDocument();
reportClientDoc.open("My crystal report.rpt", 0);
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream)reportClientDoc.getPrintOutputController().export(ReportExportFormat.PDF);
reportClientDoc.close();

The report I dealt with was developed by another team, and expected a data source called ‘TESTDB’ to be available. Without that data source present, I would get the following error:

com.crystaldecisions.sdk.occa.report.lib.ReportSDKException: Error finding JNDI name (TESTDB)---- Error code:-2147467259 Error code name:failed


Setting a data source up in Tomcat is trivial, just by adding a resource to context.xml:

<Context>
<WatchedResource>WEB-INF/web.xml</WatchedResource>

<Resource name="jdbc/TESTDB" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="user" password="passwd" driverClassName="oracle.jdbc.OracleDriver"
url="jdbc:oracle:thin:@dbserver:1521:db1"/>
</Context>

Now the data source is set up, there’s another problem - the report expects a parameter and we get this error:

com.crystaldecisions.sdk.occa.report.lib.ReportSDKParameterFieldException: InternalFormatterException---- Error code:-2147217394 Error code name:missingParameterValueError


Adding the report parameter is simple once you know how:

ParameterFieldController paramController = reportClientDoc.getDataDefController().getParameterFieldController();
paramController.setCurrentValue("","MyParamName","MyParamValue");

There’s probably a lot of subtle information and detail missing here, but it works - I can generate the PDF via java code so its now at a stage where it can be integrated into our application.

So, the final spike test code looks like:

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<%//Crystal Java Reporting Component (JRC) imports.%>
<%@page import="com.crystaldecisions.reports.sdk.*" %>
<%@page import="com.crystaldecisions.sdk.occa.report.lib.*" %>
<%@page import="com.crystaldecisions.sdk.occa.report.exportoptions.*" %>

<%//Java imports. %>
<%@page import="java.io.*" %>

<%

try {

//Open report.
ReportClientDocument reportClientDoc = new ReportClientDocument();
reportClientDoc.open("MyReport.rpt", 0);
ParameterFieldController paramController = reportClientDoc.getDataDefController().getParameterFieldController();
paramController.setCurrentValue("","MyParamName","MyParamValue");
ByteArrayInputStream byteArrayInputStream = (ByteArrayInputStream)reportClientDoc.getPrintOutputController().export(ReportExportFormat.PDF);
reportClientDoc.close();

writeToBrowser(byteArrayInputStream, response, "application/pdf");

} catch(Exception ex) {
out.println(ex);
}
%>

<%!
/*
* Utility method that demonstrates how to write an input stream to the server's local file system.
*/
private void writeToBrowser(ByteArrayInputStream byteArrayInputStream, HttpServletResponse response, String mimetype) throws Exception {

//Create a byte[] the same size as the exported ByteArrayInputStream.
byte[] buffer = new byte[byteArrayInputStream.available()];
int bytesRead = 0;

//Set response headers to indicate mime type and inline file.
response.reset();
response.setHeader("Content-disposition", "inline;filename=report.pdf");
response.setContentType(mimetype);

//Stream the byte array to the client.
while((bytesRead = byteArrayInputStream.read(buffer)) != -1) {
response.getOutputStream().write(buffer, 0, bytesRead);
}

//Flush and close the output stream.
response.getOutputStream().flush();
response.getOutputStream().close();

}
%>

Note, implementing this in a JSP was just a simple and quick shortcut to investigate the API.

I put a simple CRConfig.xml in WEB-INF/classes:

<?xml version="1.0" encoding="utf-8"?>
<CrystalReportEngine-configuration>
<reportlocation>..</reportlocation>
<timeout>0</timeout>
<ExternalFunctionLibraryClassNames>
<classname></classname>
</ExternalFunctionLibraryClassNames>
</CrystalReportEngine-configuration>


Note that this specified a report location of .. which meant I had to put MyReport.rpt in the WEB-INF directory of my application (i.e. [web-root]/WEB-INF/classes/.. = [web-root]/WEB-INF) - contents of the WEB-INF directory should not be available for download.

If the rpt file cannot be found, you’ll see an error like:

com.crystaldecisions.sdk.occa.report.lib.ReportSDKException: Report file /[path-to-webapps]/webapps/cr/WEB-INF/MyReport.rpt not found---- Error code:-2147215356 Error code name:fileNotOpened

2 comments:

  1. Hi, a really nice article.
    Do you think this can be integrated into GWT project or this is entirely for .JSP pages? Was not able to find anything for CR+GWT..

    ReplyDelete
  2. Can you please list the jar files names.

    ReplyDelete