View Javadoc

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