001/*
002 * Copyright 2008 The Kuali Foundation
003 * 
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 * http://www.opensource.org/licenses/ecl2.php
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.sys.service.impl;
017
018import java.io.ByteArrayOutputStream;
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.text.MessageFormat;
025import java.util.Arrays;
026import java.util.Date;
027import java.util.List;
028import java.util.Map;
029
030import net.sf.jasperreports.engine.JRDataSource;
031import net.sf.jasperreports.engine.JRException;
032import net.sf.jasperreports.engine.JasperCompileManager;
033import net.sf.jasperreports.engine.JasperRunManager;
034import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
035
036import org.apache.commons.lang.StringUtils;
037import org.kuali.ole.sys.OLEConstants;
038import org.kuali.ole.sys.OLEConstants.ReportGeneration;
039import org.kuali.ole.sys.service.ReportGenerationService;
040import org.kuali.rice.core.api.datetime.DateTimeService;
041import org.springframework.core.io.ClassPathResource;
042import org.springframework.ui.jasperreports.JasperReportsUtils;
043
044/**
045 * To provide utilities that can generate reports with JasperReport
046 */
047public class ReportGenerationServiceImpl implements ReportGenerationService {
048    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReportGenerationServiceImpl.class);
049
050    protected DateTimeService dateTimeService;
051    
052    public final static String PARAMETER_NAME_SUBREPORT_DIR = ReportGeneration.PARAMETER_NAME_SUBREPORT_DIR;
053    public final static String PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME = ReportGeneration.PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME;
054
055    public final static String DESIGN_FILE_EXTENSION = ReportGeneration.DESIGN_FILE_EXTENSION;
056    public final static String JASPER_REPORT_EXTENSION = ReportGeneration.JASPER_REPORT_EXTENSION;
057    public final static String PDF_FILE_EXTENSION = ReportGeneration.PDF_FILE_EXTENSION;
058    
059    public final static String SEPARATOR = "/";
060
061    /**
062     * @see org.kuali.ole.sys.batch.service.ReportGenerationService#generateReportToPdfFile(java.util.Map, java.lang.String, java.lang.String)
063     */
064    public void generateReportToPdfFile(Map<String, Object> reportData, String template, String reportFileName) {
065        List<String> data = Arrays.asList(OLEConstants.EMPTY_STRING);
066        JRDataSource dataSource = new JRBeanCollectionDataSource(data);
067
068        generateReportToPdfFile(reportData, dataSource, template, reportFileName);
069    }
070
071    /**
072     * The dataSource can be an instance of JRDataSource, java.util.Collection or object array.
073     * 
074     * @see org.kuali.ole.sys.batch.service.ReportGenerationService#generateReportToPdfFile(java.util.Map, java.lang.Object, java.lang.String,
075     *      java.lang.String)
076     */
077    public void generateReportToPdfFile(Map<String, Object> reportData, Object dataSource, String template, String reportFileName) {
078        ClassPathResource resource = getReportTemplateClassPathResource(template);
079        if (resource == null || !resource.exists()) {
080            throw new IllegalArgumentException("Cannot find the template file: " + template);
081        }
082
083        try {
084            if (reportData != null && reportData.containsKey(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME)) {
085                Map<String, String> subReports = (Map<String, String>) reportData.get(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME);
086                String subReportDirectory = (String) reportData.get(PARAMETER_NAME_SUBREPORT_DIR);
087                compileSubReports(subReports, subReportDirectory);
088            }
089
090            String realTemplateNameWithoutExtension = removeTemplateExtension(resource);
091            String designTemplateName = realTemplateNameWithoutExtension.concat(DESIGN_FILE_EXTENSION);
092            String jasperReportName = realTemplateNameWithoutExtension.concat(JASPER_REPORT_EXTENSION);
093            compileReportTemplate(designTemplateName, jasperReportName);
094
095            JRDataSource jrDataSource = JasperReportsUtils.convertReportData(dataSource);
096
097            reportFileName = reportFileName + PDF_FILE_EXTENSION;
098            File reportDirectory = new File(StringUtils.substringBeforeLast(reportFileName, SEPARATOR));
099            if(!reportDirectory.exists()) {
100                reportDirectory.mkdir();
101            }
102            
103            JasperRunManager.runReportToPdfFile(jasperReportName, reportFileName, reportData, jrDataSource);
104        }
105        catch (Exception e) {
106            LOG.error(e);
107            throw new RuntimeException("Fail to generate report.", e);
108        }
109    }
110
111    /**
112     * @see org.kuali.ole.sys.batch.service.ReportGenerationService#generateReportToOutputStream(java.util.Map, java.lang.Object,
113     *      java.lang.String, java.io.ByteArrayOutputStream)
114     */
115    public void generateReportToOutputStream(Map<String, Object> reportData, Object dataSource, String template, ByteArrayOutputStream baos) {
116        ClassPathResource resource = getReportTemplateClassPathResource(template);
117        if (resource == null || !resource.exists()) {
118            throw new IllegalArgumentException("Cannot find the template file: " + template);
119        }
120
121        try {
122            if (reportData != null && reportData.containsKey(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME)) {
123                Map<String, String> subReports = (Map<String, String>) reportData.get(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME);
124                String subReportDirectory = (String) reportData.get(PARAMETER_NAME_SUBREPORT_DIR);
125                compileSubReports(subReports, subReportDirectory);
126            }
127
128            String realTemplateNameWithoutExtension = removeTemplateExtension(resource);
129            String designTemplateName = realTemplateNameWithoutExtension.concat(DESIGN_FILE_EXTENSION);
130            String jasperReportName = realTemplateNameWithoutExtension.concat(JASPER_REPORT_EXTENSION);
131            compileReportTemplate(designTemplateName, jasperReportName);
132
133            JRDataSource jrDataSource = JasperReportsUtils.convertReportData(dataSource);
134
135            InputStream inputStream = new FileInputStream(jasperReportName);
136
137            JasperRunManager.runReportToPdfStream(inputStream, (OutputStream) baos, reportData, jrDataSource);
138        }
139        catch (Exception e) {
140            LOG.error(e);
141            throw new RuntimeException("Fail to generate report.", e);
142        }
143    }
144
145    /**
146     * @see org.kuali.ole.sys.batch.service.ReportGenerationService#buildFullFileName(java.util.Date, java.lang.String, java.lang.String,
147     *      java.lang.String)
148     */
149    public String buildFullFileName(Date runDate, String directory, String fileName, String extension) {
150        String runtimeStamp = dateTimeService.toDateTimeStringForFilename(runDate);
151        String fileNamePattern = "{0}" + SEPARATOR + "{1}_{2}{3}";
152
153        return MessageFormat.format(fileNamePattern, directory, fileName, runtimeStamp, extension);
154    }
155
156    /**
157     * get a class path resource that references to the given report template
158     * 
159     * @param reportTemplateName the given report template name with its full-qualified package name. It may not include extension.
160     *        If an extension is included in the name, it should be prefixed ".jasper" or '.jrxml".
161     * @return a class path resource that references to the given report template
162     */
163    protected ClassPathResource getReportTemplateClassPathResource(String reportTemplateName) {
164        if (reportTemplateName.endsWith(DESIGN_FILE_EXTENSION) || reportTemplateName.endsWith(JASPER_REPORT_EXTENSION)) {
165            return new ClassPathResource(reportTemplateName);
166        }
167
168        String jasperReport = reportTemplateName.concat(JASPER_REPORT_EXTENSION);
169        ClassPathResource resource = new ClassPathResource(jasperReport);
170        if (resource.exists()) {
171            return resource;
172        }
173
174        String designTemplate = reportTemplateName.concat(DESIGN_FILE_EXTENSION);
175        resource = new ClassPathResource(designTemplate);
176        return resource;
177    }
178
179    /**
180     * complie the report template xml file into a Jasper report file if the compiled file does not exist or is out of update
181     * 
182     * @param designTemplate the full name of the report template xml file
183     * @param jasperReport the full name of the compiled report file
184     */
185    protected void compileReportTemplate(String designTemplate, String jasperReport) throws JRException {
186        File jasperFile = new File(jasperReport);
187        File designFile = new File(designTemplate);
188
189        if (!jasperFile.exists() && !designFile.exists()) {
190            throw new RuntimeException("Both the design template file and jasper report file don't exist: (" + designTemplate + ", " + jasperReport + ")");
191        }
192
193        if (!jasperFile.exists() && designFile.exists()) {
194            JasperCompileManager.compileReportToFile(designTemplate, jasperReport);
195        }
196        else if (jasperFile.exists() && designFile.exists()) {
197            if (jasperFile.lastModified() < designFile.lastModified()) {
198                JasperCompileManager.compileReportToFile(designTemplate, jasperReport);
199            }
200        }
201    }
202
203    /**
204     * compile the given sub reports
205     * 
206     * @param subReports the sub report Map that hold the sub report templete names indexed with keys
207     * @param subReportDirectory the directory where sub report templates are located
208     */
209    protected void compileSubReports(Map<String, String> subReports, String subReportDirectory) throws Exception {
210        for (Map.Entry<String, String> entry: subReports.entrySet()) {
211            ClassPathResource resource = getReportTemplateClassPathResource(subReportDirectory + entry.getValue());
212            String realTemplateNameWithoutExtension = removeTemplateExtension(resource);
213
214            String designTemplateName = realTemplateNameWithoutExtension.concat(DESIGN_FILE_EXTENSION);
215            String jasperReportName = realTemplateNameWithoutExtension.concat(JASPER_REPORT_EXTENSION);
216
217            compileReportTemplate(designTemplateName, jasperReportName);
218        }
219    }
220
221    /**
222     * remove the file extension of the given template if any
223     * 
224     * @param template the given template
225     * @return the template without file extension
226     */
227    protected String removeTemplateExtension(ClassPathResource template) throws IOException {
228        String realTemplateName = template.getFile().getAbsolutePath();
229
230        int lastIndex = realTemplateName.lastIndexOf(".");
231        String realTemplateNameWithoutExtension = lastIndex > 0 ? realTemplateName.substring(0, lastIndex) : realTemplateName;
232
233        return realTemplateNameWithoutExtension;
234    }
235
236    /**
237     * Sets the DateTimeService
238     * 
239     * @param dateTimeService The dateTimeService to set.
240     */
241    public void setDateTimeService(DateTimeService dateTimeService) {
242        this.dateTimeService = dateTimeService;
243    }
244}