View Javadoc

1   /*
2    * $Id: JasperReportsResult.java 670172 2008-06-21 10:12:56Z hermanns $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  package org.apache.struts2.views.jasperreports;
23  
24  import java.io.ByteArrayOutputStream;
25  import java.io.File;
26  import java.io.IOException;
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.TimeZone;
30  
31  import javax.servlet.ServletContext;
32  import javax.servlet.ServletException;
33  import javax.servlet.ServletOutputStream;
34  import javax.servlet.http.HttpServletRequest;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import net.sf.jasperreports.engine.JRException;
38  import net.sf.jasperreports.engine.JRExporter;
39  import net.sf.jasperreports.engine.JRExporterParameter;
40  import net.sf.jasperreports.engine.JRParameter;
41  import net.sf.jasperreports.engine.JasperFillManager;
42  import net.sf.jasperreports.engine.JasperPrint;
43  import net.sf.jasperreports.engine.JasperReport;
44  import net.sf.jasperreports.engine.export.JRCsvExporter;
45  import net.sf.jasperreports.engine.export.JRCsvExporterParameter;
46  import net.sf.jasperreports.engine.export.JRHtmlExporter;
47  import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
48  import net.sf.jasperreports.engine.export.JRPdfExporter;
49  import net.sf.jasperreports.engine.export.JRRtfExporter;
50  import net.sf.jasperreports.engine.export.JRXlsExporter;
51  import net.sf.jasperreports.engine.export.JRXmlExporter;
52  import net.sf.jasperreports.engine.util.JRLoader;
53  
54  import org.apache.struts2.ServletActionContext;
55  import org.apache.struts2.dispatcher.StrutsResultSupport;
56  
57  import com.opensymphony.xwork2.ActionInvocation;
58  import com.opensymphony.xwork2.util.TextUtils;
59  import com.opensymphony.xwork2.util.ValueStack;
60  import com.opensymphony.xwork2.util.logging.Logger;
61  import com.opensymphony.xwork2.util.logging.LoggerFactory;
62  
63  /***
64   * <!-- START SNIPPET: description -->
65   * <p/>
66   * Generates a JasperReports report using the specified format or PDF if no
67   * format is specified.
68   * <p/>
69   * <!-- END SNIPPET: description -->
70   * <p />
71   * <b>This result type takes the following parameters:</b>
72   * <p/>
73   * <!-- START SNIPPET: params -->
74   * <p/>
75   * <ul>
76   * <p/>
77   * <li><b>location (default)</b> - the location where the compiled jasper report
78   * definition is (foo.jasper), relative from current URL.</li>
79   * <p/>
80   * <li><b>dataSource (required)</b> - the EL expression used to retrieve the
81   * datasource from the value stack (usually a List).</li>
82   * <p/>
83   * <li><b>parse</b> - true by default. If set to false, the location param will
84   * not be parsed for EL expressions.</li>
85   * <p/>
86   * <li><b>format</b> - the format in which the report should be generated. Valid
87   * values can be found in {@link JasperReportConstants}. If no format is
88   * specified, PDF will be used.</li>
89   * <p/>
90   * <li><b>contentDisposition</b> - disposition (defaults to "inline", values are
91   * typically <i>filename="document.pdf"</i>).</li>
92   * <p/>
93   * <li><b>documentName</b> - name of the document (will generate the http header
94   * <code>Content-disposition = X; filename=X.[format]</code>).</li>
95   * <p/>
96   * <li><b>delimiter</b> - the delimiter used when generating CSV reports. By
97   * default, the character used is ",".</li>
98   * <p/>
99   * <li><b>imageServletUrl</b> - name of the url that, when prefixed with the
100  * context page, can return report images.</li>
101  * <p/>
102  * <li>
103  * <b>reportParameters</b> - (2.1.2+) OGNL expression used to retrieve a map of
104  * report parameters from the value stack. The parameters may be accessed
105  * in the report via the usual JR mechanism and might include data not
106  * part of the dataSource, such as the user name of the report creator, etc.
107  * </li>
108  * <p/>
109  * <li>
110  * <b>exportParameters</b> - (2.1.2+) OGNL expression used to retrieve a map of
111  * JR exporter parameters from the value stack. The export parameters are
112  * used to customize the JR export. For example, a PDF export might enable
113  * encryption and set the user password to a string known to the report creator.
114  * </li>
115  * <p/>
116  * </ul>
117  * <p/>
118  * <p>
119  * This result follows the same rules from {@link StrutsResultSupport}.
120  * Specifically, all parameters will be parsed if the "parse" parameter
121  * is not set to false.
122  * </p>
123  * <!-- END SNIPPET: params -->
124  * <p/>
125  * <b>Example:</b>
126  * <p/>
127  * <pre><!-- START SNIPPET: example1 -->
128  * &lt;result name="success" type="jasper"&gt;
129  *   &lt;param name="location"&gt;foo.jasper&lt;/param&gt;
130  *   &lt;param name="dataSource"&gt;mySource&lt;/param&gt;
131  *   &lt;param name="format"&gt;CSV&lt;/param&gt;
132  * &lt;/result&gt;
133  * <!-- END SNIPPET: example1 --></pre>
134  * or for pdf
135  * <pre><!-- START SNIPPET: example2 -->
136  * &lt;result name="success" type="jasper"&gt;
137  *   &lt;param name="location"&gt;foo.jasper&lt;/param&gt;
138  *   &lt;param name="dataSource"&gt;mySource&lt;/param&gt;
139  * &lt;/result&gt;
140  * <!-- END SNIPPET: example2 --></pre>
141  */
142 public class JasperReportsResult extends StrutsResultSupport implements JasperReportConstants {
143 
144     private static final long serialVersionUID = -2523174799621182907L;
145 
146     private final static Logger LOG = LoggerFactory.getLogger(JasperReportsResult.class);
147 
148     protected String dataSource;
149     protected String format;
150     protected String documentName;
151     protected String contentDisposition;
152     protected String delimiter;
153     protected String imageServletUrl = "/images/";
154     protected String timeZone;
155 
156     /***
157      * Names a report parameters map stack value, allowing
158      * additional report parameters from the action.
159      */
160     protected String reportParameters;
161 
162     /***
163      * Names an exporter parameters map stack value,
164      * allowing the use of custom export parameters.
165      */
166     protected String exportParameters;
167 
168     /***
169      * Default ctor.
170      */
171     public JasperReportsResult() {
172         super();
173     }
174 
175     /***
176      * Default ctor with location.
177      *
178      * @param location Result location.
179      */
180     public JasperReportsResult(String location) {
181         super(location);
182     }
183 
184     public String getImageServletUrl() {
185         return imageServletUrl;
186     }
187 
188     public void setImageServletUrl(final String imageServletUrl) {
189         this.imageServletUrl = imageServletUrl;
190     }
191 
192     public void setDataSource(String dataSource) {
193         this.dataSource = dataSource;
194     }
195 
196     public void setFormat(String format) {
197         this.format = format;
198     }
199 
200     public void setDocumentName(String documentName) {
201         this.documentName = documentName;
202     }
203 
204     public void setContentDisposition(String contentDisposition) {
205         this.contentDisposition = contentDisposition;
206     }
207 
208     public void setDelimiter(String delimiter) {
209         this.delimiter = delimiter;
210     }
211 
212     /***
213      * set time zone id
214      *
215      * @param timeZone
216      */
217     public void setTimeZone(final String timeZone) {
218         this.timeZone = timeZone;
219     }
220 
221     public String getReportParameters() {
222         return reportParameters;
223     }
224 
225     public void setReportParameters(String reportParameters) {
226         this.reportParameters = reportParameters;
227     }
228 
229     public String getExportParameters() {
230         return exportParameters;
231     }
232 
233     public void setExportParameters(String exportParameters) {
234         this.exportParameters = exportParameters;
235     }
236 
237     protected void doExecute(String finalLocation, ActionInvocation invocation) throws Exception {
238         // Will throw a runtime exception if no "datasource" property. TODO Best place for that is...?
239         initializeProperties(invocation);
240 
241         if (LOG.isDebugEnabled()) {
242             LOG.debug("Creating JasperReport for dataSource = " + dataSource + ", format = " + format);
243         }
244 
245         HttpServletRequest request = (HttpServletRequest) invocation.getInvocationContext().get(ServletActionContext.HTTP_REQUEST);
246         HttpServletResponse response = (HttpServletResponse) invocation.getInvocationContext().get(ServletActionContext.HTTP_RESPONSE);
247 
248         // Handle IE special case: it sends a "contype" request first.
249         // TODO Set content type to config settings?
250         if ("contype".equals(request.getHeader("User-Agent"))) {
251             try {
252                 response.setContentType("application/pdf");
253                 response.setContentLength(0);
254 
255                 ServletOutputStream outputStream = response.getOutputStream();
256                 outputStream.close();
257             } catch (IOException e) {
258                 LOG.error("Error writing report output", e);
259                 throw new ServletException(e.getMessage(), e);
260             }
261             return;
262         }
263 
264         // Construct the data source for the report.
265         ValueStack stack = invocation.getStack();
266         ValueStackDataSource stackDataSource = new ValueStackDataSource(stack, dataSource);
267 
268         // Determine the directory that the report file is in and set the reportDirectory parameter
269         // For WW 2.1.7:
270         //  ServletContext servletContext = ((ServletConfig) invocation.getInvocationContext().get(ServletActionContext.SERVLET_CONFIG)).getServletContext();
271         ServletContext servletContext = (ServletContext) invocation.getInvocationContext().get(ServletActionContext.SERVLET_CONTEXT);
272         String systemId = servletContext.getRealPath(finalLocation);
273         Map parameters = new ValueStackShadowMap(stack);
274         File directory = new File(systemId.substring(0, systemId.lastIndexOf(File.separator)));
275         parameters.put("reportDirectory", directory);
276         parameters.put(JRParameter.REPORT_LOCALE, invocation.getInvocationContext().getLocale());
277 
278         // put timezone in jasper report parameter
279         if (timeZone != null) {
280             timeZone = conditionalParse(timeZone, invocation);
281             final TimeZone tz = TimeZone.getTimeZone(timeZone);
282             if (tz != null) {
283                 // put the report time zone
284                 parameters.put(JRParameter.REPORT_TIME_ZONE, tz);
285             }
286         }
287 
288         // Add any report parameters from action to param map.
289         Map reportParams = (Map) stack.findValue(reportParameters);
290         if (reportParams != null) {
291             LOG.debug("Found report parameters; adding to parameters...");
292             parameters.putAll(reportParams);
293         }
294 
295         byte[] output;
296         JasperPrint jasperPrint;
297 
298         // Fill the report and produce a print object
299         try {
300             JasperReport jasperReport = (JasperReport) JRLoader.loadObject(systemId);
301             jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, stackDataSource);
302         } catch (JRException e) {
303             LOG.error("Error building report for uri " + systemId, e);
304             throw new ServletException(e.getMessage(), e);
305         }
306 
307         // Export the print object to the desired output format
308         try {
309             if (contentDisposition != null || documentName != null) {
310                 final StringBuffer tmp = new StringBuffer();
311                 tmp.append((contentDisposition == null) ? "inline" : contentDisposition);
312 
313                 if (documentName != null) {
314                     tmp.append("; filename=");
315                     tmp.append(documentName);
316                     tmp.append(".");
317                     tmp.append(format.toLowerCase());
318                 }
319 
320                 response.setHeader("Content-disposition", tmp.toString());
321             }
322 
323             JRExporter exporter;
324 
325             if (format.equals(FORMAT_PDF)) {
326                 response.setContentType("application/pdf");
327                 exporter = new JRPdfExporter();
328             } else if (format.equals(FORMAT_CSV)) {
329                 response.setContentType("text/plain");
330                 exporter = new JRCsvExporter();
331             } else if (format.equals(FORMAT_HTML)) {
332                 response.setContentType("text/html");
333 
334                 // IMAGES_MAPS seems to be only supported as "backward compatible" from JasperReports 1.1.0
335 
336                 Map imagesMap = new HashMap();
337                 request.getSession(true).setAttribute("IMAGES_MAP", imagesMap);
338 
339                 exporter = new JRHtmlExporter();
340                 exporter.setParameter(JRHtmlExporterParameter.IMAGES_MAP, imagesMap);
341                 exporter.setParameter(JRHtmlExporterParameter.IMAGES_URI, request.getContextPath() + imageServletUrl);
342 
343                 // Needed to support chart images:
344                 exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
345                 request.getSession().setAttribute("net.sf.jasperreports.j2ee.jasper_print", jasperPrint);
346             } else if (format.equals(FORMAT_XLS)) {
347                 response.setContentType("application/vnd.ms-excel");
348                 exporter = new JRXlsExporter();
349             } else if (format.equals(FORMAT_XML)) {
350                 response.setContentType("text/xml");
351                 exporter = new JRXmlExporter();
352             } else if (format.equals(FORMAT_RTF)) {
353                 response.setContentType("application/rtf");
354                 exporter = new JRRtfExporter();
355             } else {
356                 throw new ServletException("Unknown report format: " + format);
357             }
358 
359             Map exportParams = (Map) stack.findValue(exportParameters);
360             if (exportParams != null) {
361                 LOG.debug("Found export parameters; adding to exporter parameters...");
362                 exporter.getParameters().putAll(exportParams);
363             }
364 
365             output = exportReportToBytes(jasperPrint, exporter);
366         } catch (JRException e) {
367             String message = "Error producing " + format + " report for uri " + systemId;
368             LOG.error(message, e);
369             throw new ServletException(e.getMessage(), e);
370         }
371 
372         response.setContentLength(output.length);
373 
374         // Will throw ServletException on IOException.
375         writeReport(response, output);
376     }
377 
378     /***
379      * Writes report bytes to response output stream.
380      *
381      * @param response Current response.
382      * @param output   Report bytes to write.
383      * @throws ServletException on stream IOException.
384      */
385     private void writeReport(HttpServletResponse response, byte[] output) throws ServletException {
386         ServletOutputStream outputStream = null;
387         try {
388             outputStream = response.getOutputStream();
389             outputStream.write(output);
390             outputStream.flush();
391         } catch (IOException e) {
392             LOG.error("Error writing report output", e);
393             throw new ServletException(e.getMessage(), e);
394         } finally {
395             try {
396                 if (outputStream != null) {
397                     outputStream.close();
398                 }
399             } catch (IOException e) {
400                 LOG.error("Error closing report output stream", e);
401                 throw new ServletException(e.getMessage(), e);
402             }
403         }
404     }
405 
406     /***
407      * Sets up result properties, parsing etc.
408      *
409      * @param invocation Current invocation.
410      * @throws Exception on initialization error.
411      */
412     private void initializeProperties(ActionInvocation invocation) throws Exception {
413         if (dataSource == null) {
414             String message = "No dataSource specified...";
415             LOG.error(message);
416             throw new RuntimeException(message);
417         }
418         dataSource = conditionalParse(dataSource, invocation);
419 
420         format = conditionalParse(format, invocation);
421         if (!TextUtils.stringSet(format)) {
422             format = FORMAT_PDF;
423         }
424 
425         if (contentDisposition != null) {
426             contentDisposition = conditionalParse(contentDisposition, invocation);
427         }
428 
429         if (documentName != null) {
430             documentName = conditionalParse(documentName, invocation);
431         }
432 
433         reportParameters = conditionalParse(reportParameters, invocation);
434         exportParameters = conditionalParse(exportParameters, invocation);
435     }
436 
437     /***
438      * Run a Jasper report to CSV format and put the results in a byte array
439      *
440      * @param jasperPrint The Print object to render as CSV
441      * @param exporter    The exporter to use to export the report
442      * @return A CSV formatted report
443      * @throws net.sf.jasperreports.engine.JRException
444      *          If there is a problem running the report
445      */
446     private byte[] exportReportToBytes(JasperPrint jasperPrint, JRExporter exporter) throws JRException {
447         byte[] output;
448         ByteArrayOutputStream baos = new ByteArrayOutputStream();
449 
450         exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
451         exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, baos);
452         if (delimiter != null) {
453             exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, delimiter);
454         }
455 
456         exporter.exportReport();
457 
458         output = baos.toByteArray();
459 
460         return output;
461     }
462 
463 }