001    /*
002     * Copyright 2011 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     */
016    package org.kuali.kfs.sys.service.impl;
017    
018    import java.io.ByteArrayOutputStream;
019    import java.io.File;
020    import java.io.FileInputStream;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.io.OutputStream;
024    import java.text.MessageFormat;
025    import java.util.Arrays;
026    import java.util.Date;
027    import java.util.List;
028    import java.util.Map;
029    
030    import net.sf.jasperreports.engine.JRDataSource;
031    import net.sf.jasperreports.engine.JRException;
032    import net.sf.jasperreports.engine.JasperCompileManager;
033    import net.sf.jasperreports.engine.JasperRunManager;
034    import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
035    
036    import org.apache.commons.lang.StringUtils;
037    import org.kuali.kfs.sys.KFSConstants;
038    import org.kuali.kfs.sys.KFSConstants.ReportGeneration;
039    import org.kuali.kfs.sys.service.ReportGenerationService;
040    import org.kuali.rice.kns.service.DateTimeService;
041    import org.springframework.core.io.ClassPathResource;
042    import org.springframework.ui.jasperreports.JasperReportsUtils;
043    
044    /**
045     * To provide utilities that can generate reports with JasperReport
046     */
047    public 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.kfs.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(KFSConstants.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.kfs.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.kfs.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.kfs.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    }