/* * Copyright (c) 2014 Red Hat, Inc. and/or its affiliates. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Cheng Fang - Initial API and implementation */ package org.jberet.support.io; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; import javax.batch.api.BatchProperty; import javax.batch.api.Batchlet; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Instance; import javax.inject.Inject; import javax.inject.Named; import net.sf.jasperreports.engine.JRDataSource; import net.sf.jasperreports.engine.JREmptyDataSource; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.JasperPrint; import net.sf.jasperreports.engine.JasperRunManager; import net.sf.jasperreports.engine.data.JRAbstractTextDataSource; import net.sf.jasperreports.engine.data.JRCsvDataSource; import net.sf.jasperreports.engine.data.JRXlsxDataSource; import net.sf.jasperreports.engine.data.JRXmlDataSource; import net.sf.jasperreports.engine.data.JsonDataSource; import net.sf.jasperreports.engine.data.XlsDataSource; import net.sf.jasperreports.export.Exporter; import net.sf.jasperreports.export.SimpleExporterInput; import org.jberet.support._private.SupportMessages; /** * A batchlet that generates report using Jasper Reports. Configuration of Jasper Reports is done through either batch * properties in job xml and {@code @BatchProperty} injections, or through CDI injections of objects created and * configured by other parts of the application. Batch properties generally have higher precedence than CDI-injected * counterparts. * <p> * The Jasper Reports data source is configured through either {@link #resource} property, or {@link #jrDataSourceInstance} * injection. Directly passing a {@code java.sql.Connection} instance to this class is not supported. For JDBC * data source, The application should instead inject {@code net.sf.jasperreports.engine.JRResultSetDataSource} * into {@link #jrDataSourceInstance}. * <p> * Report can be saved to a file, or directed to a {@code java.io.OutputStream}. * <p> * This class supports all output report formats implemented in Jasper Reports. If {@link #exporterInstance} field is * injected with an instance of {@code net.sf.jasperreports.export.Exporter}, it will be used to export and generate the * final report. Otherwise, if {@link #outputType} batch property is set to pdf, html, or jrprint, a PDF, HTML or * Jasper jrprint report is generated respectively. * * @since 1.1.0 */ @Named @Dependent public class JasperReportsBatchlet implements Batchlet { protected static final String DEFAULT_OUTPUT_TYPE = "pdf"; /** * The resource that provides the data source for generating report. Optional property, and defaults to null. * If specified, it may be a URL, a file path, or any resource path that can be loaded by the current application * class loader. If this property is not specified, the application should inject appropriate {@code JRDataSource} * into {@link #jrDataSourceInstance}. If neither {@link #resource} nor {@link #jrDataSourceInstance} is * specified, an {@code net.sf.jasperreports.engine.JREmptyDataSource} is used. * <p> * {@code JRDataSource} injection allows for more flexible instantiation and configuration, such as setting * locale, datePattern, numberPattern, timeZone, recordDelimiter, useFirstRowAsHeader, columnNames, fieldDelimiter, * etc, before making the instance available to this class. * <p> * This property has higher precedence than {@link #jrDataSourceInstance} injection. * * @see #jrDataSourceInstance */ @Inject @BatchProperty protected String resource; /** * If {@link #resource} is specified, and is a csv resource, this property specifies the delimiter between records, * typically new line character (CR/LF). Optional property. See {@code net.sf.jasperreports.engine.data.JRCsvDataSource} * for details. */ @Inject @BatchProperty protected String recordDelimiter; /** * If {@link #resource} is specified, and is a csv, xls, or xlsx resource, this property specifies whether to use * the first row as header. Optional property and valid values are "true" and "false". * See {@code net.sf.jasperreports.engine.data.JRCsvDataSource} or * {@code net.sf.jasperreports.engine.data.AbstractXlsDataSource} for details. */ @Inject @BatchProperty protected String useFirstRowAsHeader; /** * If {@link #resource} is specified, and is a CSV resource, this property specifies the field or column delimiter. * Optional property. See {@code net.sf.jasperreports.engine.data.JRCsvDataSource} for details. */ @Inject @BatchProperty protected String fieldDelimiter; /** * If {@link #resource} is specified, and is a csv, xls, or xlsx resource, this property specifies an array of * strings representing column names matching field names in the report template. Optional property. * See {@code net.sf.jasperreports.engine.data.JRCsvDataSource} or * {@code net.sf.jasperreports.engine.data.AbstractXlsDataSource}for details. */ @Inject @BatchProperty protected String[] columnNames; /** * If {@link #resource} is specified, this property specifies the date pattern string value. Optional property. * See {@code net.sf.jasperreports.engine.data.JRAbstractTextDataSource#setDatePattern(java.lang.String)} for details. */ @Inject @BatchProperty protected String datePattern; /** * If {@link #resource} is specified, this property specifies the number pattern string value. Optional property. * See {@code net.sf.jasperreports.engine.data.JRAbstractTextDataSource#setNumberPattern(java.lang.String)} for details. */ @Inject @BatchProperty protected String numberPattern; /** * If {@link #resource} is specified, this property specifies the time zone string value. Optional property. * See {@code net.sf.jasperreports.engine.data.JRAbstractTextDataSource#setTimeZone(java.lang.String)} for details. */ @Inject @BatchProperty protected String timeZone; /** * If {@link #resource} is specified, this property specifies the locale string value. Optional property. * See {@code net.sf.jasperreports.engine.data.JRAbstractTextDataSource#setLocale(java.lang.String)} for details. */ @Inject @BatchProperty protected String locale; /** * If {@link #resource} is specified, and is a csv resource, this property specifies the charset name for reading * the csv resource. Optional property. * See {@code net.sf.jasperreports.engine.data.JRCsvDataSource#JRCsvDataSource(java.io.File, java.lang.String)} * for detail. */ @Inject @BatchProperty protected String charset; /** * Resource path of the compiled Jasper Reports template (*.jasper file). Required property. It may be a URL, * a file path, or any resource path that can be loaded by the current application class loader. */ @Inject @BatchProperty protected String template; /** * The format of report output. Optional property and defaults to {@value #DEFAULT_OUTPUT_TYPE}. Valid values are: * <ul> * <li>pdf * <li>html * <li>jrprint * </ul> * If other formats are desired, the application should inject into {@link #exporterInstance} with a properly * configured instance of {@code net.sf.jasperreports.export.Exporter}. */ @Inject @BatchProperty protected String outputType; /** * The file path of the generated report. Optional property and defaults to null. When this property is not * specified, the application should inject an {@code java.io.OutputStream} into {@link #outputStreamInstance}. * For instance, in order to stream the report to servlet response, a {@code javax.servlet.ServletOutputStream} * can be injected into {@link #outputStreamInstance}. * <p> * This property has higher precedence than {@link #outputStreamInstance} injection. * * @see #outputStreamInstance */ @Inject @BatchProperty protected String outputFile; /** * Report parameters for generating the report. Optional property and defaults to null. This property can be used * to specify string-based key-value pairs as report parameters. For more complex report parameters with object * types, use injection into {@link #reportParametersInstance}. * <p> * This property has higher precedence than {@link #reportParametersInstance} injection. * * @see #reportParametersInstance */ @Inject @BatchProperty protected Map reportParameters; /** * Optional injection of report output stream, which allows for more control over the output stream creation, * sharing, and configuration. The injected {@code OutputStream} is closed at the end of {@link #process()} method. * * @see #outputFile */ @Inject protected Instance<OutputStream> outputStreamInstance; /** * Optional injection of Jasper Reports {@code net.sf.jasperreports.engine.JRDataSource}, which allows for more * control over the {@code JRDataSource} creation and configuration. * * @see #resource */ @Inject protected Instance<JRDataSource> jrDataSourceInstance; /** * Optional injection of Jasper Reports report parameters, which allows for more complex, non-string values. * * @see #reportParameters */ @Inject protected Instance<Map<String, Object>> reportParametersInstance; /** * Optional injection of an implementation of Jasper Reports {@code net.sf.jasperreports.export.Exporter}. The * injected instance should have been properly configured, including appropriate * {@code net.sf.jasperreports.export.ExporterOutput}. {@code net.sf.jasperreports.export.ExporterInput} will be * set to a {@code net.sf.jasperreports.engine.JasperPrint} by this class. * <p> * Some built-in implementations of {@code net.sf.jasperreports.export.ExporterOutput}: * <ul> * <li>{@code net.sf.jasperreports.engine.export.JRPdfExporter} * <li>{@code net.sf.jasperreports.engine.export.HtmlExporter} * <li>{@code net.sf.jasperreports.engine.export.ooxml.JRDocxExporter} * <li>{@code net.sf.jasperreports.engine.export.ooxml.JRPptxExporter} * <li>{@code net.sf.jasperreports.engine.export.JRXlsExporter} * <li>{@code net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter} * <li>{@code net.sf.jasperreports.engine.export.JRTextExporter} * <li>{@code net.sf.jasperreports.engine.export.JRRtfExporter} * <li>{@code net.sf.jasperreports.engine.export.JRXmlExporter} * <li>{@code net.sf.jasperreports.engine.export.JRCsvExporter} * <li>{@code net.sf.jasperreports.engine.export.JsonExporter} * <li>{@code net.sf.jasperreports.engine.export.oasis.JROdsExporter} * <li>{@code net.sf.jasperreports.engine.export.oasis.JROdtExporter} * </ul> */ @Inject protected Instance<Exporter> exporterInstance; private InputStream resourceInputStream; private String templateFilePath; @Override public String process() throws Exception { InputStream templateInputStream = null; OutputStream outputStream = null; try { if (template == null || !template.toLowerCase().endsWith(".jasper")) { //if the template file in *.jrxml, or *.xml format, need to compile the xml design file into // serialized "*.jasper" report file throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, template, "template (*.jasper)"); } templateInputStream = getTemplateInputStream(); outputStream = getOutputStream(); final String ftype = outputType == null ? DEFAULT_OUTPUT_TYPE : outputType.toLowerCase(); final JRDataSource jrDataSource = getJrDataSource(); final Map<String, Object> reportParameters1 = getReportParameters(); final Exporter exporter = getExporter(); if (exporter != null) { //injected Exporter instance should already set appropriate ExporterOutput, so pass null to fillAndExportReport(...) fillAndExportReport(templateInputStream, reportParameters1, jrDataSource, exporter); } else { if (ftype.equals("pdf")) { JasperRunManager.runReportToPdfStream(templateInputStream, outputStream, reportParameters1, jrDataSource); outputStream.flush(); } else if (ftype.equals("html")) { JasperRunManager.runReportToHtmlFile(getTemplateFilePath(templateInputStream), outputFile, reportParameters1, jrDataSource); } else if (ftype.equals("jrprint")) { JasperFillManager.fillReportToFile(getTemplateFilePath(templateInputStream), outputFile, reportParameters1, jrDataSource); } else { throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, outputFile, "outputFile"); } } return null; } finally { if (templateInputStream != null) { try { templateInputStream.close(); } catch (final IOException e) { //ignore } } if (resourceInputStream != null) { try { resourceInputStream.close(); } catch (final IOException e) { //ignore } } if (outputStream != null) { try { outputStream.close(); } catch (final IOException e) { //ignore } } } } @Override public void stop() throws Exception { } protected InputStream getTemplateInputStream() { return ItemReaderWriterBase.getInputStream(template, false); } protected OutputStream getOutputStream() throws FileNotFoundException { if (outputFile != null) { return new FileOutputStream(outputFile); } // if output needs to be directed to an injected OutputStream if (outputStreamInstance != null && !outputStreamInstance.isUnsatisfied()) { return outputStreamInstance.get(); } return null; //throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, outputFile, "outputFile"); } protected Map<String, Object> getReportParameters() { if (reportParameters != null) { return reportParameters; } if (reportParametersInstance != null && !reportParametersInstance.isUnsatisfied()) { return reportParametersInstance.get(); } return new HashMap<String, Object>(); } protected JRDataSource getJrDataSource() throws IOException, JRException { if (resource != null) { final String res = resource.toLowerCase(); resourceInputStream = ItemReaderWriterBase.getInputStream(resource, false); if (res.endsWith(".csv")) { final JRCsvDataSource csvDataSource = charset == null ? new JRCsvDataSource(resourceInputStream) : new JRCsvDataSource(resourceInputStream, charset); setCommonJRDataSourceProperties(csvDataSource); if (useFirstRowAsHeader != null) { csvDataSource.setUseFirstRowAsHeader(Boolean.parseBoolean(useFirstRowAsHeader)); } if (recordDelimiter != null) { csvDataSource.setRecordDelimiter(recordDelimiter); } if (fieldDelimiter != null) { csvDataSource.setFieldDelimiter(fieldDelimiter.trim().charAt(0)); } if (columnNames != null) { csvDataSource.setColumnNames(columnNames); } return csvDataSource; } if (res.endsWith(".xls")) { final XlsDataSource xlsDataSource = new XlsDataSource(resourceInputStream); setCommonJRDataSourceProperties(xlsDataSource); if (columnNames != null) { xlsDataSource.setColumnNames(columnNames); } if (useFirstRowAsHeader != null) { xlsDataSource.setUseFirstRowAsHeader(Boolean.parseBoolean(useFirstRowAsHeader)); } return xlsDataSource; } if (res.endsWith(".xlsx")) { final JRXlsxDataSource jrXlsxDataSource = new JRXlsxDataSource(resourceInputStream); setCommonJRDataSourceProperties(jrXlsxDataSource); if (columnNames != null) { jrXlsxDataSource.setColumnNames(columnNames); } if (useFirstRowAsHeader != null) { jrXlsxDataSource.setUseFirstRowAsHeader(Boolean.parseBoolean(useFirstRowAsHeader)); } return jrXlsxDataSource; } if (res.endsWith(".xml")) { final JRXmlDataSource jrXmlDataSource = new JRXmlDataSource(resourceInputStream); setCommonJRDataSourceProperties(jrXmlDataSource); return jrXmlDataSource; } if (res.endsWith(".json")) { final JsonDataSource jsonDataSource = new JsonDataSource(resourceInputStream); setCommonJRDataSourceProperties(jsonDataSource); return jsonDataSource; } throw SupportMessages.MESSAGES.invalidReaderWriterProperty(null, resource, "resource"); } else { if (jrDataSourceInstance != null && !jrDataSourceInstance.isUnsatisfied()) { return jrDataSourceInstance.get(); } } return new JREmptyDataSource(); } protected Exporter getExporter() throws Exception { if (exporterInstance != null && !exporterInstance.isUnsatisfied()) { return exporterInstance.get(); } return null; } protected void fillAndExportReport(final InputStream templateInputStream, final Map<String, Object> reportParameters, final JRDataSource jrDataSource, final Exporter exporter) throws JRException { final JasperPrint jasperPrint = JasperFillManager.fillReport(templateInputStream, reportParameters, jrDataSource); exporter.setExporterInput(new SimpleExporterInput(jasperPrint)); exporter.exportReport(); } private String getTemplateFilePath(final InputStream templateInputStream) throws IOException { if (templateFilePath != null) { return templateFilePath; } File templateAsFile = new File(template); if (templateAsFile.exists()) { return templateFilePath = template; } //the template file path is unknown, need to save it to a file first final byte[] buffer = new byte[102400]; templateAsFile = File.createTempFile("jberet-support-JasperReportsBatchlet", String.valueOf(System.currentTimeMillis())); FileOutputStream out = null; try { out = new FileOutputStream(templateAsFile); int len; while ((len = templateInputStream.read(buffer)) != -1) { out.write(buffer, 0, len); } } finally { if (out != null) { try { out.close(); } catch (final IOException e) { //ignore } } } return templateFilePath = templateAsFile.getPath(); } private void setCommonJRDataSourceProperties(final JRAbstractTextDataSource jrDataSource) { if (locale != null) { jrDataSource.setLocale(locale); } if (timeZone != null) { jrDataSource.setTimeZone(timeZone); } if (numberPattern != null) { jrDataSource.setNumberPattern(numberPattern); } if (datePattern != null) { jrDataSource.setDatePattern(datePattern); } } }