View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.kfs.sys.service.impl;
20  
21  import java.io.ByteArrayOutputStream;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.OutputStream;
27  import java.text.MessageFormat;
28  import java.util.Arrays;
29  import java.util.Date;
30  import java.util.List;
31  import java.util.Map;
32  
33  import net.sf.jasperreports.engine.JRDataSource;
34  import net.sf.jasperreports.engine.JRException;
35  import net.sf.jasperreports.engine.JasperCompileManager;
36  import net.sf.jasperreports.engine.JasperRunManager;
37  import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
38  
39  import org.apache.commons.lang.StringUtils;
40  import org.kuali.kfs.sys.KFSConstants;
41  import org.kuali.kfs.sys.KFSConstants.ReportGeneration;
42  import org.kuali.kfs.sys.service.ReportGenerationService;
43  import org.kuali.rice.core.api.datetime.DateTimeService;
44  import org.springframework.core.io.ClassPathResource;
45  import org.springframework.ui.jasperreports.JasperReportsUtils;
46  
47  /**
48   * To provide utilities that can generate reports with JasperReport
49   */
50  public class ReportGenerationServiceImpl implements ReportGenerationService {
51      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReportGenerationServiceImpl.class);
52  
53      protected DateTimeService dateTimeService;
54      
55      public final static String PARAMETER_NAME_SUBREPORT_DIR = ReportGeneration.PARAMETER_NAME_SUBREPORT_DIR;
56      public final static String PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME = ReportGeneration.PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME;
57  
58      public final static String DESIGN_FILE_EXTENSION = ReportGeneration.DESIGN_FILE_EXTENSION;
59      public final static String JASPER_REPORT_EXTENSION = ReportGeneration.JASPER_REPORT_EXTENSION;
60      public final static String PDF_FILE_EXTENSION = ReportGeneration.PDF_FILE_EXTENSION;
61      
62      public final static String SEPARATOR = "/";
63  
64      /**
65       * @see org.kuali.kfs.sys.batch.service.ReportGenerationService#generateReportToPdfFile(java.util.Map, java.lang.String, java.lang.String)
66       */
67      public void generateReportToPdfFile(Map<String, Object> reportData, String template, String reportFileName) {
68          List<String> data = Arrays.asList(KFSConstants.EMPTY_STRING);
69          JRDataSource dataSource = new JRBeanCollectionDataSource(data);
70  
71          generateReportToPdfFile(reportData, dataSource, template, reportFileName);
72      }
73  
74      /**
75       * The dataSource can be an instance of JRDataSource, java.util.Collection or object array.
76       * 
77       * @see org.kuali.kfs.sys.batch.service.ReportGenerationService#generateReportToPdfFile(java.util.Map, java.lang.Object, java.lang.String,
78       *      java.lang.String)
79       */
80      public void generateReportToPdfFile(Map<String, Object> reportData, Object dataSource, String template, String reportFileName) {
81          ClassPathResource resource = getReportTemplateClassPathResource(template);
82          if (resource == null || !resource.exists()) {
83              throw new IllegalArgumentException("Cannot find the template file: " + template);
84          }
85  
86          try {
87              if (reportData != null && reportData.containsKey(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME)) {
88                  Map<String, String> subReports = (Map<String, String>) reportData.get(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME);
89                  String subReportDirectory = (String) reportData.get(PARAMETER_NAME_SUBREPORT_DIR);
90                  compileSubReports(subReports, subReportDirectory);
91              }
92  
93              String realTemplateNameWithoutExtension = removeTemplateExtension(resource);
94              String designTemplateName = realTemplateNameWithoutExtension.concat(DESIGN_FILE_EXTENSION);
95              String jasperReportName = realTemplateNameWithoutExtension.concat(JASPER_REPORT_EXTENSION);
96              compileReportTemplate(designTemplateName, jasperReportName);
97  
98              JRDataSource jrDataSource = JasperReportsUtils.convertReportData(dataSource);
99  
100             reportFileName = reportFileName + PDF_FILE_EXTENSION;
101             File reportDirectory = new File(StringUtils.substringBeforeLast(reportFileName, SEPARATOR));
102             if(!reportDirectory.exists()) {
103                 reportDirectory.mkdir();
104             }
105             
106             JasperRunManager.runReportToPdfFile(jasperReportName, reportFileName, reportData, jrDataSource);
107         }
108         catch (Exception e) {
109             LOG.error(e);
110             throw new RuntimeException("Fail to generate report.", e);
111         }
112     }
113 
114     /**
115      * @see org.kuali.kfs.sys.batch.service.ReportGenerationService#generateReportToOutputStream(java.util.Map, java.lang.Object,
116      *      java.lang.String, java.io.ByteArrayOutputStream)
117      */
118     public void generateReportToOutputStream(Map<String, Object> reportData, Object dataSource, String template, ByteArrayOutputStream baos) {
119         ClassPathResource resource = getReportTemplateClassPathResource(template);
120         if (resource == null || !resource.exists()) {
121             throw new IllegalArgumentException("Cannot find the template file: " + template);
122         }
123 
124         try {
125             if (reportData != null && reportData.containsKey(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME)) {
126                 Map<String, String> subReports = (Map<String, String>) reportData.get(PARAMETER_NAME_SUBREPORT_TEMPLATE_NAME);
127                 String subReportDirectory = (String) reportData.get(PARAMETER_NAME_SUBREPORT_DIR);
128                 compileSubReports(subReports, subReportDirectory);
129             }
130 
131             String realTemplateNameWithoutExtension = removeTemplateExtension(resource);
132             String designTemplateName = realTemplateNameWithoutExtension.concat(DESIGN_FILE_EXTENSION);
133             String jasperReportName = realTemplateNameWithoutExtension.concat(JASPER_REPORT_EXTENSION);
134             compileReportTemplate(designTemplateName, jasperReportName);
135 
136             JRDataSource jrDataSource = JasperReportsUtils.convertReportData(dataSource);
137 
138             InputStream inputStream = new FileInputStream(jasperReportName);
139 
140             JasperRunManager.runReportToPdfStream(inputStream, (OutputStream) baos, reportData, jrDataSource);
141         }
142         catch (Exception e) {
143             LOG.error(e);
144             throw new RuntimeException("Fail to generate report.", e);
145         }
146     }
147 
148     /**
149      * @see org.kuali.kfs.sys.batch.service.ReportGenerationService#buildFullFileName(java.util.Date, java.lang.String, java.lang.String,
150      *      java.lang.String)
151      */
152     public String buildFullFileName(Date runDate, String directory, String fileName, String extension) {
153         String runtimeStamp = dateTimeService.toDateTimeStringForFilename(runDate);
154         String fileNamePattern = "{0}" + SEPARATOR + "{1}_{2}{3}";
155 
156         return MessageFormat.format(fileNamePattern, directory, fileName, runtimeStamp, extension);
157     }
158 
159     /**
160      * get a class path resource that references to the given report template
161      * 
162      * @param reportTemplateName the given report template name with its full-qualified package name. It may not include extension.
163      *        If an extension is included in the name, it should be prefixed ".jasper" or '.jrxml".
164      * @return a class path resource that references to the given report template
165      */
166     protected ClassPathResource getReportTemplateClassPathResource(String reportTemplateName) {
167         if (reportTemplateName.endsWith(DESIGN_FILE_EXTENSION) || reportTemplateName.endsWith(JASPER_REPORT_EXTENSION)) {
168             return new ClassPathResource(reportTemplateName);
169         }
170 
171         String jasperReport = reportTemplateName.concat(JASPER_REPORT_EXTENSION);
172         ClassPathResource resource = new ClassPathResource(jasperReport);
173         if (resource.exists()) {
174             return resource;
175         }
176 
177         String designTemplate = reportTemplateName.concat(DESIGN_FILE_EXTENSION);
178         resource = new ClassPathResource(designTemplate);
179         return resource;
180     }
181 
182     /**
183      * complie the report template xml file into a Jasper report file if the compiled file does not exist or is out of update
184      * 
185      * @param designTemplate the full name of the report template xml file
186      * @param jasperReport the full name of the compiled report file
187      */
188     protected void compileReportTemplate(String designTemplate, String jasperReport) throws JRException {
189         File jasperFile = new File(jasperReport);
190         File designFile = new File(designTemplate);
191 
192         if (!jasperFile.exists() && !designFile.exists()) {
193             throw new RuntimeException("Both the design template file and jasper report file don't exist: (" + designTemplate + ", " + jasperReport + ")");
194         }
195 
196         if (!jasperFile.exists() && designFile.exists()) {
197             JasperCompileManager.compileReportToFile(designTemplate, jasperReport);
198         }
199         else if (jasperFile.exists() && designFile.exists()) {
200             if (jasperFile.lastModified() < designFile.lastModified()) {
201                 JasperCompileManager.compileReportToFile(designTemplate, jasperReport);
202             }
203         }
204     }
205 
206     /**
207      * compile the given sub reports
208      * 
209      * @param subReports the sub report Map that hold the sub report templete names indexed with keys
210      * @param subReportDirectory the directory where sub report templates are located
211      */
212     protected void compileSubReports(Map<String, String> subReports, String subReportDirectory) throws Exception {
213         for (Map.Entry<String, String> entry: subReports.entrySet()) {
214             ClassPathResource resource = getReportTemplateClassPathResource(subReportDirectory + entry.getValue());
215             String realTemplateNameWithoutExtension = removeTemplateExtension(resource);
216 
217             String designTemplateName = realTemplateNameWithoutExtension.concat(DESIGN_FILE_EXTENSION);
218             String jasperReportName = realTemplateNameWithoutExtension.concat(JASPER_REPORT_EXTENSION);
219 
220             compileReportTemplate(designTemplateName, jasperReportName);
221         }
222     }
223 
224     /**
225      * remove the file extension of the given template if any
226      * 
227      * @param template the given template
228      * @return the template without file extension
229      */
230     protected String removeTemplateExtension(ClassPathResource template) throws IOException {
231         String realTemplateName = template.getFile().getAbsolutePath();
232 
233         int lastIndex = realTemplateName.lastIndexOf(".");
234         String realTemplateNameWithoutExtension = lastIndex > 0 ? realTemplateName.substring(0, lastIndex) : realTemplateName;
235 
236         return realTemplateNameWithoutExtension;
237     }
238 
239     /**
240      * Sets the DateTimeService
241      * 
242      * @param dateTimeService The dateTimeService to set.
243      */
244     public void setDateTimeService(DateTimeService dateTimeService) {
245         this.dateTimeService = dateTimeService;
246     }
247 }