View Javadoc

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