/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.sys.service.impl;
import java.io.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperRunManager;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSConstants.ReportGeneration;
import org.kuali.kfs.sys.service.ReportGenerationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ui.jasperreports.JasperReportsUtils;
/**
* To provide utilities that can generate reports with JasperReport
*/
public class ReportGenerationServiceImpl implements ReportGenerationService {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReportGenerationServiceImpl.class);
protected DateTimeService dateTimeService;
/**
* @see org.kuali.kfs.sys.batch.service.ReportGenerationService#generateReportToPdfFile(java.util.Map, java.lang.String, java.lang.String)
*/
public void generateReportToPdfFile(Map<String, Object> reportData, String template, String reportFileName) {
List<String> data = Arrays.asList(KFSConstants.EMPTY_STRING);
JRDataSource dataSource = new JRBeanCollectionDataSource(data);
generateReportToPdfFile(reportData, dataSource, template, reportFileName);
}
/**
* The dataSource can be an instance of JRDataSource, java.util.Collection or object array.
*
* @see org.kuali.kfs.sys.batch.service.ReportGenerationService#generateReportToPdfFile(java.util.Map, java.lang.Object, java.lang.String,
* java.lang.String)
*/
public void generateReportToPdfFile(Map<String, Object> reportData, Object dataSource, String template, String reportFileName) {
ClassPathResource resource = getReportTemplateClassPathResource(template.concat(ReportGeneration.DESIGN_FILE_EXTENSION));
if (resource == null || !resource.exists()) {
throw new IllegalArgumentException("Cannot find the template file: " + template.concat(ReportGeneration.DESIGN_FILE_EXTENSION));
}
try {
if (reportData != null && reportData.containsKey(ReportGeneration.PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME)) {
Map<String, String> subReports = (Map<String, String>) reportData.get(ReportGeneration.PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME);
String subReportDirectory = (String) reportData.get(ReportGeneration.PARAMETER_NAME_SUBREPORT_DIR);
compileSubReports(subReports, subReportDirectory);
}
String designTemplateName = template.concat(ReportGeneration.DESIGN_FILE_EXTENSION);
InputStream jasperReport = new FileInputStream(compileReportTemplate(designTemplateName));
JRDataSource jrDataSource = JasperReportsUtils.convertReportData(dataSource);
reportFileName = reportFileName + ReportGeneration.PDF_FILE_EXTENSION;
File reportDirectory = new File(StringUtils.substringBeforeLast(reportFileName, File.separator));
if(!reportDirectory.exists()) {
reportDirectory.mkdir();
}
JasperRunManager.runReportToPdfStream(jasperReport, new FileOutputStream(reportFileName), decorateReportData(reportData), jrDataSource);
}
catch (Exception e) {
LOG.error(e);
throw new RuntimeException("Fail to generate report.", e);
}
}
/**
* Updates the report data map with any values that report generation needs (for instance, substituting in the temporary directory into the report subdirectory)
* @param reportData the original report data
* @return a decorated version of report data
*/
protected Map<String, Object> decorateReportData(Map<String, Object> reportData) {
Map<String, Object> decoratedReportData = new ConcurrentHashMap<>();
decoratedReportData.putAll(reportData);
decoratedReportData.put(ReportGeneration.PARAMETER_NAME_SUBREPORT_DIR, new File(System.getProperty("java.io.tmpdir").concat(File.separator).concat(reportData.get(ReportGeneration.PARAMETER_NAME_SUBREPORT_DIR).toString())).getAbsolutePath().concat(File.separator));
return decoratedReportData;
}
/**
* @see org.kuali.kfs.sys.batch.service.ReportGenerationService#generateReportToOutputStream(java.util.Map, java.lang.Object,
* java.lang.String, java.io.ByteArrayOutputStream)
*/
public void generateReportToOutputStream(Map<String, Object> reportData, Object dataSource, String template, ByteArrayOutputStream baos) {
ClassPathResource resource = getReportTemplateClassPathResource(template.concat(ReportGeneration.DESIGN_FILE_EXTENSION));
if (resource == null || !resource.exists()) {
throw new IllegalArgumentException("Cannot find the template file: " + template.concat(ReportGeneration.DESIGN_FILE_EXTENSION));
}
try {
if (reportData != null && reportData.containsKey(ReportGeneration.PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME)) {
Map<String, String> subReports = (Map<String, String>) reportData.get(ReportGeneration.PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME);
String subReportDirectory = (String) reportData.get(ReportGeneration.PARAMETER_NAME_SUBREPORT_DIR);
compileSubReports(subReports, subReportDirectory);
}
String designTemplateName = template.concat(ReportGeneration.DESIGN_FILE_EXTENSION);
InputStream jasperReport = new FileInputStream(compileReportTemplate(designTemplateName));
JRDataSource jrDataSource = JasperReportsUtils.convertReportData(dataSource);
JasperRunManager.runReportToPdfStream(jasperReport, baos, decorateReportData(reportData), jrDataSource);
}
catch (Exception e) {
LOG.error(e);
throw new RuntimeException("Fail to generate report.", e);
}
}
/**
* @see org.kuali.kfs.sys.batch.service.ReportGenerationService#buildFullFileName()
*/
@Override
public String buildFullFileName(Date runDate, String directory, String fileName, String extension) {
String runtimeStamp = dateTimeService.toDateTimeStringForFilename(runDate);
String fileNamePattern = "{0}" + File.separator + "{1}_{2}{3}";
return MessageFormat.format(fileNamePattern, directory, fileName, runtimeStamp, extension);
}
/**
* get a class path resource that references to the given report template
*
* @param reportTemplateName the given report template name with its full-qualified package name. It may not include extension.
* If an extension is included in the name, it should be prefixed ".jasper" or '.jrxml".
* @return a class path resource that references to the given report template
*/
protected ClassPathResource getReportTemplateClassPathResource(String reportTemplateName) {
if (reportTemplateName.endsWith(ReportGeneration.DESIGN_FILE_EXTENSION) || reportTemplateName.endsWith(ReportGeneration.JASPER_REPORT_EXTENSION)) {
return new ClassPathResource(reportTemplateName);
}
String jasperReport = reportTemplateName.concat(ReportGeneration.JASPER_REPORT_EXTENSION);
ClassPathResource resource = new ClassPathResource(jasperReport);
if (resource.exists()) {
return resource;
}
String designTemplate = reportTemplateName.concat(ReportGeneration.DESIGN_FILE_EXTENSION);
resource = new ClassPathResource(designTemplate);
return resource;
}
/**
* complie the report template xml file into a Jasper report file if the compiled file does not exist or is out of update
*
* @param template the name of the template file, without an extension
* @return an input stream where the intermediary report was written
*/
protected File compileReportTemplate(String template) throws JRException, IOException {
ClassPathResource designTemplateResource = new ClassPathResource(template);
if (!designTemplateResource.exists()) {
throw new RuntimeException("The design template file does not exist: "+template);
}
File tempJasperDir = new File(System.getProperty("java.io.tmpdir")+File.separator+template.replaceAll("\\/[^\\/]+$", ""));
if (!tempJasperDir.exists()) {
FileUtils.forceMkdir(tempJasperDir);
}
File tempJasperFile = new File(System.getProperty("java.io.tmpdir")+File.separator+template.replace(ReportGeneration.DESIGN_FILE_EXTENSION,"").concat(ReportGeneration.JASPER_REPORT_EXTENSION));
if (!tempJasperFile.exists()) {
JasperCompileManager.compileReportToStream(designTemplateResource.getInputStream(), new FileOutputStream(tempJasperFile));
}
return tempJasperFile;
}
/**
* compile the given sub reports
*
* @param subReports the sub report Map that hold the sub report templete names indexed with keys
* @param subReportDirectory the directory where sub report templates are located
*/
protected void compileSubReports(Map<String, String> subReports, String subReportDirectory) throws Exception {
for (Map.Entry<String, String> entry: subReports.entrySet()) {
final String designTemplateName = subReportDirectory + entry.getValue() + ReportGeneration.DESIGN_FILE_EXTENSION;
compileReportTemplate(designTemplateName);
}
}
/**
* Sets the DateTimeService
*
* @param dateTimeService The dateTimeService to set.
*/
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
}