Hi.
There's a slight performace problem with JRResource, that it creates
JasperReport in it's constructor. I've 10 resource links on one page so
it means that 10 JasperReports will be initialized everytime page is
created, even if none of them gets clicked. The performance penalty is
not small - 400ms.
So I think the JasperReport should be initialized lazyly, only when
the resource is really used.
I've atached "fixed" JRResource, it may not be the best solution, but
it works for me. (Look in the constructor(s) to see how it works)
-Matej
/*
* $Id: JRResource.java,v 1.1 2005/09/20 20:00:46 eelco12 Exp $
* $Revision: 1.1 $
* $Date: 2005/09/20 20:00:46 $
*
*
==============================================================================
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package framework.wicket.jr;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.sql.Connection;
import java.util.Map;
import net.sf.jasperreports.engine.JRAbstractExporter;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.util.JRLoader;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import wicket.WicketRuntimeException;
import wicket.protocol.http.WebResponse;
import wicket.resource.DynamicByteArrayResource;
/**
* Base class for jasper reports resources.
*
* @author Eelco Hillenius
*/
public abstract class JRResource extends DynamicByteArrayResource
{
/** logger. */
private static Log log = LogFactory.getLog(JRResource.class);
/**
* Provides JDBC connection.
*/
public static interface IDatabaseConnectionProvider
{
/**
* Gets a JDBC connection to use when filling the report.
*
* @return a JDBC connection
*/
Connection get();
/**
* Called when the report is generated and the connection can be
* released again.
*/
void release();
}
/** the compiled report this resource references. */
private JasperReport jasperReport;
/** the report parameters. */
private Map reportParameters;
/** the datasource if any for filling this report. */
private JRDataSource reportDataSource;
/** the connection provider if any for filling this report. */
private IDatabaseConnectionProvider connectionProvider;
private static abstract class JasperReportInitializer {
abstract JasperReport createJasperReport() throws JRException;
};
/** initializer for delayed report initialization. */
private JasperReportInitializer initializer;
/**
* When set, a header 'Content-Disposition: attachment;
* filename="${fileName}"' will be added to the response, resulting in a
* download dialog. No magical extensions are added, so you should make
sure
* the file has the extension you want yourself.
*/
private String fileName;
/**
* Construct without a report. You must provide a report before you can
use
* this resource.
*/
public JRResource()
{
super();
setCacheable(false);
}
/**
* Construct.
*
* @param report
* the report input stream
*/
public JRResource(final InputStream report)
{
super();
setCacheable(false);
initializer = new JasperReportInitializer() {
JasperReport createJasperReport() throws JRException {
return (JasperReport)
JRLoader.loadObject(report);
};
};
}
/**
* Construct.
*
* @param report
* the report input stream
*/
public JRResource(final URL report)
{
super();
setCacheable(false);
initializer = new JasperReportInitializer() {
JasperReport createJasperReport() throws JRException {
return (JasperReport)
JRLoader.loadObject(report);
};
};
}
/**
* Construct.
*
* @param report
* the report input stream
*/
public JRResource(final File report)
{
super();
setCacheable(false);
initializer = new JasperReportInitializer() {
JasperReport createJasperReport() throws JRException {
return (JasperReport)
JRLoader.loadObject(report);
};
};
}
/**
* Gets jasperReport.
*
* @return jasperReport
*/
public JasperReport getJasperReport()
{
return jasperReport;
}
/**
* Sets {bjasperReport.
*
* @param jasperReport
* jasperReport
*/
public final void setJasperReport(JasperReport jasperReport)
{
this.jasperReport = jasperReport;
}
/**
* Gets the report parameters.
*
* @return report parameters
*/
public Map getReportParameters()
{
return reportParameters;
}
/**
* Sets the report parameters.
*
* @param reportParameters
* report parameters
* @return This
*/
public final JRResource setReportParameters(Map reportParameters)
{
this.reportParameters = reportParameters;
return this;
}
/**
* Gets the datasource if any for filling this report.
*
* @return the datasource if any for filling this report
*/
public JRDataSource getReportDataSource()
{
return reportDataSource;
}
/**
* Sets the datasource if any for filling this report.
*
* @param reportDataSource
* the datasource if any for filling this report
* @return This
*/
public JRResource setReportDataSource(JRDataSource reportDataSource)
{
this.reportDataSource = reportDataSource;
return this;
}
/**
* Gets the connection provider if any for filling this report.
*
* @return the connection provider if any for filling this report
*/
public IDatabaseConnectionProvider getConnectionProvider()
{
return connectionProvider;
}
/**
* Sets the connection provider if any for filling this report.
*
* @param connectionProvider
* the connection provider if any for filling this report
* @return This
*/
public final JRResource setConnectionProvider(
IDatabaseConnectionProvider connectionProvider)
{
this.connectionProvider = connectionProvider;
return this;
}
/**
* Gets the file name. When set, a header 'Content-Disposition:
attachment;
* filename="${fileName}"' will be added to the response, resulting in a
* download dialog. No magical extensions are added, so you should make
sure
* the file has the extension you want yourself.
*
* @return the file name
*/
public String getFileName()
{
return fileName;
}
/**
* Sets the file name. When set, a header 'Content-Disposition:
attachment;
* filename="${fileName}"' will be added to the response, resulting in a
* download dialog. No magical extensions are added, so you should make
sure
* the file has the extension you want yourself.
*
* @param fileName
* the file name
* @return This
*/
public final JRResource setFileName(String fileName)
{
this.fileName = fileName;
return this;
}
/**
* Called by getData to obtain an exporter instance.
* @return an exporter instance
*/
protected abstract JRAbstractExporter newExporter();
/**
* Gets the binary data by getting a new instance of JasperPrint and an
* exporter for generating the output.
*
* @return the binary data
*
* @see wicket.resource.DynamicByteArrayResource#getData()
*/
protected byte[] getData()
{
try
{
long t1 = System.currentTimeMillis();
// get a print instance for exporting
JasperPrint print = newJasperPrint();
// get a fresh instance of an exporter for this report
JRAbstractExporter exporter = newExporter();
// prepare a stream to trap the exporter's output
ByteArrayOutputStream baos = new
ByteArrayOutputStream();
exporter.setParameter(JRExporterParameter.JASPER_PRINT,
print);
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
// execute the export and return the trapped result
exporter.exportReport();
byte[] data = baos.toByteArray();
if (log.isDebugEnabled())
{
long t2 = System.currentTimeMillis();
log.debug("loaded report data; bytes: "
+ data.length + " in " + (t2 -
t1) + " miliseconds");
}
return data;
}
catch (JRException e)
{
throw new WicketRuntimeException(e);
}
}
/**
* Creates a new [EMAIL PROTECTED] JasperPrint} instance. This instance
is specific for
* this render, but it not yet designated for one output format only.
*
* @return a new [EMAIL PROTECTED] JasperPrint} instance.
* @throws JRException
*/
protected JasperPrint newJasperPrint() throws JRException
{
final JasperPrint jasperPrint;
// if the report has not yet been initialized and can be,
initialize it
if (getJasperReport() == null && initializer != null) {
try {
setJasperReport(initializer.createJasperReport());
} catch (JRException e) {
throw new WicketRuntimeException(e);
}
}
JasperReport jasperReport = getJasperReport();
Map reportParameters = getReportParameters();
JRDataSource reportDataSource = getReportDataSource();
if (reportDataSource != null)
{
jasperPrint =
JasperFillManager.fillReport(jasperReport, reportParameters,
reportDataSource);
}
else
{
IDatabaseConnectionProvider connectionProvider = null;
try
{
connectionProvider = getConnectionProvider();
if (connectionProvider == null)
{
throw new IllegalStateException(
"JasperReportsResources
must either have a JRDataSource, "
+ "or a
JDBC Connection provided");
}
jasperPrint =
JasperFillManager.fillReport(jasperReport,
reportParameters,
connectionProvider.get());
}
finally
{
if (connectionProvider != null)
{
connectionProvider.release();
}
}
}
return jasperPrint;
}
/**
* @see
wicket.markup.html.WebResource#setHeaders(wicket.protocol.http.WebResponse)
*/
protected void setHeaders(WebResponse response)
{
super.setHeaders(response);
String fileName = getFileName();
if (fileName != null)
{
response.setHeader("Content-Disposition", "attachment;
filename=\""
+ fileName + "\"");
}
}
}