KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > springframework > web > servlet > view > jasperreports > AbstractJasperReportsView


1 /*
2  * Copyright 2002-2006 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.springframework.web.servlet.view.jasperreports;
18
19 import java.io.IOException JavaDoc;
20 import java.lang.reflect.Field JavaDoc;
21 import java.sql.Connection JavaDoc;
22 import java.sql.SQLException JavaDoc;
23 import java.util.Collection JavaDoc;
24 import java.util.Enumeration JavaDoc;
25 import java.util.HashMap JavaDoc;
26 import java.util.Iterator JavaDoc;
27 import java.util.Locale JavaDoc;
28 import java.util.Map JavaDoc;
29 import java.util.Properties JavaDoc;
30 import java.util.ResourceBundle JavaDoc;
31
32 import javax.servlet.http.HttpServletRequest JavaDoc;
33 import javax.servlet.http.HttpServletResponse JavaDoc;
34 import javax.sql.DataSource JavaDoc;
35
36 import net.sf.jasperreports.engine.JRDataSource;
37 import net.sf.jasperreports.engine.JRDataSourceProvider;
38 import net.sf.jasperreports.engine.JRException;
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.design.JRCompiler;
45 import net.sf.jasperreports.engine.design.JRDefaultCompiler;
46 import net.sf.jasperreports.engine.design.JasperDesign;
47 import net.sf.jasperreports.engine.util.JRLoader;
48 import net.sf.jasperreports.engine.xml.JRXmlLoader;
49
50 import org.springframework.context.ApplicationContextException;
51 import org.springframework.context.support.MessageSourceResourceBundle;
52 import org.springframework.core.io.Resource;
53 import org.springframework.ui.jasperreports.JasperReportsUtils;
54 import org.springframework.util.ClassUtils;
55 import org.springframework.util.CollectionUtils;
56 import org.springframework.web.servlet.support.RequestContextUtils;
57 import org.springframework.web.servlet.view.AbstractUrlBasedView;
58
59 /**
60  * Base class for all JasperReports views. Applies on-the-fly compilation
61  * of report designs as required and coordinates the rendering process.
62  * The resource path of the main report needs to be specified as <code>url</code>.
63  *
64  * <p>This class is responsible for getting report data from the model that has
65  * been provided to the view. The default implementation checks for a model object
66  * under the specified <code>reportDataKey</code> first, then falls back to looking
67  * for a value of type <code>JRDataSource</code>, <code>java.util.Collection</code>,
68  * object array (in that order).
69  *
70  * <p>If no <code>JRDataSource</code> can be found in the model, then reports will
71  * be filled using the configured <code>javax.sql.DataSource</code> if any. If neither
72  * a <code>JRDataSource</code> or <code>javax.sql.DataSource</code> is available then
73  * an <code>IllegalArgumentException</code> is raised.
74  *
75  * <p>Provides support for sub-reports through the <code>subReportUrls</code> and
76  * <code>subReportDataKeys</code> properties.
77  *
78  * <p>When using sub-reports, the master report should be configured using the
79  * <code>url</code> property and the sub-reports files should be configured using
80  * the <code>subReportUrls</code> property. Each entry in the <code>subReportUrls</code>
81  * Map corresponds to an individual sub-report. The key of an entry must match up
82  * to a sub-report parameter in your report file of type
83  * <code>net.sf.jasperreports.engine.JasperReport</code>,
84  * and the value of an entry must be the URL for the sub-report file.
85  *
86  * <p>For sub-reports that require an instance of <code>JRDataSource</code>, that is,
87  * they don't have a hard-coded query for data retrieval, you can include the
88  * appropriate data in your model as would with the data source for the parent report.
89  * However, you must provide a List of parameter names that need to be converted to
90  * <code>JRDataSource</code> instances for the sub-report via the
91  * <code>subReportDataKeys</code> property. When using <code>JRDataSource</code>
92  * instances for sub-reports, you <i>must</i> specify a value for the
93  * <code>reportDataKey</code> property, indicating the data to use for the main report.
94  *
95  * <p>Allows for exporter parameters to be configured declatively using the
96  * <code>exporterParameters</code> property. This is a <code>Map</code> typed
97  * property where the key of an entry corresponds to the fully-qualified name
98  * of the static field for the <code>JRExporterParameter</code> and the value
99  * of an entry is the value you want to assign to the exporter parameter.
100  *
101  * <p>Response headers can be controlled via the <code>headers</code> property. Spring
102  * will attempt to set the correct value for the <code>Content-Diposition</code> header
103  * so that reports render correctly in Internet Explorer. However, you can override this
104  * setting through the <code>headers</code> property.
105  *
106  * @author Rob Harrop
107  * @author Juergen Hoeller
108  * @since 1.1.3
109  * @see #setUrl
110  * @see #setReportDataKey
111  * @see #setSubReportUrls
112  * @see #setSubReportDataKeys
113  * @see #setHeaders
114  * @see #setExporterParameters
115  * @see #setJdbcDataSource
116  */

117 public abstract class AbstractJasperReportsView extends AbstractUrlBasedView {
118
119     /**
120      * Constant that defines "Content-Disposition" header.
121      */

122     protected static final String JavaDoc HEADER_CONTENT_DISPOSITION = "Content-Disposition";
123
124     /**
125      * The default Content-Disposition header. Used to make IE play nice.
126      */

127     protected static final String JavaDoc CONTENT_DISPOSITION_INLINE = "inline";
128
129
130     /**
131      * A String key used to lookup the <code>JRDataSource</code> in the model.
132      */

133     private String JavaDoc reportDataKey;
134
135     /**
136      * Stores the paths to any sub-report files used by this top-level report,
137      * along with the keys they are mapped to in the top-level report file.
138      */

139     private Properties JavaDoc subReportUrls;
140
141     /**
142      * Stores the names of any data source objects that need to be converted to
143      * <code>JRDataSource</code> instances and included in the report parameters
144      * to be passed on to a sub-report.
145      */

146     private String JavaDoc[] subReportDataKeys;
147
148     /**
149      * Stores the headers to written with each response
150      */

151     private Properties JavaDoc headers;
152
153     /**
154      * Stores the exporter parameters passed in by the user as passed in by the user. May be keyed as
155      * <code>String</code>s with the fully qualified name of the exporter parameter field.
156      */

157     private Map JavaDoc exporterParameters = new HashMap JavaDoc();
158
159     /**
160      * Stores the converted exporter parameters - keyed by <code>JRExporterParameter</code>.
161      */

162     private Map JavaDoc convertedExporterParameters;
163
164     /**
165      * Stores the <code>DataSource</code>, if any, used as the report data source.
166      */

167     private DataSource JavaDoc jdbcDataSource;
168
169     /**
170      * Holds the JRCompiler implementation to use for compiling reports on-the-fly.
171      */

172     private JRCompiler reportCompiler = JRDefaultCompiler.getInstance();
173
174     /**
175      * The <code>JasperReport</code> that is used to render the view.
176      */

177     private JasperReport report;
178
179     /**
180      * Holds mappings between sub-report keys and <code>JasperReport</code> objects.
181      */

182     private Map JavaDoc subReports;
183
184
185     /**
186      * Set the name of the model attribute that represents the report data.
187      * If not specified, the model map will be searched for a matching value type.
188      * <p>A <code>JRDataSource</code> will be taken as-is. For other types, conversion
189      * will apply: By default, a <code>java.util.Collection</code> will be converted
190      * to <code>JRBeanCollectionDataSource</code>, and an object array to
191      * <code>JRBeanArrayDataSource</code>.
192      * <p><b>Note:</b> If you pass in a Collection or object array in the model map
193      * for use as plain report parameter, rather than as report data to extract fields
194      * from, you need to specify the key for the actual report data to use, to avoid
195      * mis-detection of report data by type.
196      * @see #convertReportData
197      * @see net.sf.jasperreports.engine.JRDataSource
198      * @see net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
199      * @see net.sf.jasperreports.engine.data.JRBeanArrayDataSource
200      */

201     public void setReportDataKey(String JavaDoc reportDataKey) {
202         this.reportDataKey = reportDataKey;
203     }
204
205     /**
206      * Specify resource paths which must be loaded as instances of
207      * <code>JasperReport</code> and passed to the JasperReports engine for
208      * rendering as sub-reports, under the same keys as in this mapping.
209      * @param subReports mapping between model keys and resource paths
210      * (Spring resource locations)
211      * @see #setUrl
212      * @see org.springframework.context.ApplicationContext#getResource
213      */

214     public void setSubReportUrls(Properties JavaDoc subReports) {
215         this.subReportUrls = subReports;
216     }
217
218     /**
219      * Set the list of names corresponding to the model parameters that will contain
220      * data source objects for use in sub-reports. Spring will convert these objects
221      * to instances of <code>JRDataSource</code> where applicable and will then
222      * include the resulting <code>JRDataSource</code> in the parameters passed into
223      * the JasperReports engine.
224      * <p>The name specified in the list should correspond to an attribute in the
225      * model Map, and to a sub-report data source parameter in your report file.
226      * If you pass in <code>JRDataSource</code> objects as model attributes,
227      * specifing this list of keys is not required.
228      * <p>If you specify a list of sub-report data keys, it is required to also
229      * specify a <code>reportDataKey</code> for the main report, to avoid confusion
230      * between the data source objects for the various reports involved.
231      * @param subReportDataKeys list of names for sub-report data source objects
232      * @see #setReportDataKey
233      * @see #convertReportData
234      * @see net.sf.jasperreports.engine.JRDataSource
235      * @see net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
236      * @see net.sf.jasperreports.engine.data.JRBeanArrayDataSource
237      */

238     public void setSubReportDataKeys(String JavaDoc[] subReportDataKeys) {
239         this.subReportDataKeys = subReportDataKeys;
240     }
241
242     /**
243      * Specify the set of headers that are included in each of response.
244      * @param headers the headers to write to each response.
245      */

246     public void setHeaders(Properties JavaDoc headers) {
247         this.headers = headers;
248     }
249
250     /**
251      * Set the exporter parameters that should be used when rendering a view.
252      * @param parameters <code>Map</code> with the fully qualified field name
253      * of the <code>JRExporterParameter</code> instance as key
254      * (e.g. "net.sf.jasperreports.engine.export.JRHtmlExporterParameter.IMAGES_URI")
255      * and the value you wish to assign to the parameter as value
256      */

257     public void setExporterParameters(Map JavaDoc parameters) {
258         // NOTE: Removed conversion from here since configuration of parameters
259
// can also happen through access to the underlying Map using
260
// getExporterParameters(). Conversion now happens in initApplicationContext,
261
// and subclasses use getConvertedExporterParameters() to access the converted
262
// parameter Map - robh.
263
this.exporterParameters = parameters;
264     }
265
266     /**
267      * Return the exporter parameters that this view uses, if any.
268      */

269     public Map JavaDoc getExporterParameters() {
270         return this.exporterParameters;
271     }
272
273     /**
274      * Allows subclasses to retrieve the converted exporter parameters.
275      */

276     protected Map JavaDoc getConvertedExporterParameters() {
277         return this.convertedExporterParameters;
278     }
279
280     /**
281      * Specify the <code>javax.sql.DataSource</code> to use for reports with
282      * embedded SQL statements.
283      */

284     public void setJdbcDataSource(DataSource JavaDoc jdbcDataSource) {
285         this.jdbcDataSource = jdbcDataSource;
286     }
287
288     /**
289      * Return the <code>javax.sql.DataSource</code> that this view uses, if any.
290      */

291     protected DataSource JavaDoc getJdbcDataSource() {
292         return this.jdbcDataSource;
293     }
294
295     /**
296      * Specify the JRCompiler implementation to use for compiling a ".jrxml"
297      * report file on-the-fly into a report class.
298      * <p>By default, a JRDefaultCompiler will be used, delegating to the
299      * Eclipse JDT compiler or the Sun JDK compiler underneath.
300      * @see net.sf.jasperreports.engine.design.JRDefaultCompiler
301      */

302     public void setReportCompiler(JRCompiler reportCompiler) {
303         this.reportCompiler = (reportCompiler != null ? reportCompiler : JRDefaultCompiler.getInstance());
304     }
305
306     /**
307      * Return the JRCompiler instance to use for compiling ".jrxml" report files.
308      */

309     protected JRCompiler getReportCompiler() {
310         return this.reportCompiler;
311     }
312
313
314     /**
315      * Checks to see that a valid report file URL is supplied in the
316      * configuration. Compiles the report file is necessary.
317      * <p/>Subclasses can add custom initialization logic by overriding
318      * the {@link #onInit} method.
319      * @see #onInit()
320      */

321     protected final void initApplicationContext() throws ApplicationContextException {
322         Resource mainReport = getApplicationContext().getResource(getUrl());
323         this.report = loadReport(mainReport);
324
325         // Load sub reports if required, and check data source parameters.
326
if (this.subReportUrls != null) {
327             if (this.subReportDataKeys != null && this.subReportDataKeys.length > 0 && this.reportDataKey == null) {
328                 throw new ApplicationContextException(
329                         "'reportDataKey' for main report is required when specifying a value for 'subReportDataKeys'");
330             }
331             this.subReports = new HashMap JavaDoc(this.subReportUrls.size());
332             for (Enumeration JavaDoc urls = this.subReportUrls.propertyNames(); urls.hasMoreElements();) {
333                 String JavaDoc key = (String JavaDoc) urls.nextElement();
334                 String JavaDoc path = this.subReportUrls.getProperty(key);
335                 Resource resource = getApplicationContext().getResource(path);
336                 this.subReports.put(key, loadReport(resource));
337             }
338         }
339
340         // Convert user-supplied exporterParameters.
341
convertExporterParameters();
342
343         if (this.headers == null) {
344             this.headers = new Properties JavaDoc();
345         }
346         if (!this.headers.containsKey(HEADER_CONTENT_DISPOSITION)) {
347             this.headers.setProperty(HEADER_CONTENT_DISPOSITION, CONTENT_DISPOSITION_INLINE);
348         }
349
350         onInit();
351     }
352
353     /**
354      * Subclasses can override this to add some custom initialization logic. Called
355      * by {@link #initApplicationContext()} as soon as all standard initialization logic
356      * has finished executing.
357      * @see #initApplicationContext()
358      */

359     protected void onInit() {
360     }
361
362     /**
363      * Converts the exporter parameters passed in by the user which may be keyed
364      * by <code>String</code>s corresponding to the fully qualified name of the
365      * <code>JRExporterParameter</code> into parameters which are keyed by
366      * <code>JRExporterParameter</code>.
367      * @see #getExporterParameter(Object)
368      */

369     protected final void convertExporterParameters() {
370         if (this.exporterParameters != null && !this.exporterParameters.isEmpty()) {
371             this.convertedExporterParameters = new HashMap JavaDoc(this.exporterParameters.size());
372             for (Iterator JavaDoc it = this.exporterParameters.entrySet().iterator(); it.hasNext();) {
373                 Map.Entry JavaDoc entry = (Map.Entry JavaDoc) it.next();
374                 JRExporterParameter exporterParameter = getExporterParameter(entry.getKey());
375                 this.convertedExporterParameters.put(exporterParameter, convertParameterValue(exporterParameter, entry.getValue()));
376             }
377         }
378     }
379
380     /**
381      * Convert the supplied parameter value into the actual type required by the
382      * corresponding {@link JRExporterParameter}.
383      * <p>The default implementation simply converts the String values "true" and
384      * "false" into corresponding <code>Boolean</code> objects, and tries to convert
385      * String values that start with a digit into <code>Integer</code> objects
386      * (simply keeping them as String if number conversion fails).
387      */

388     protected Object JavaDoc convertParameterValue(JRExporterParameter parameter, Object JavaDoc value) {
389         if (value instanceof String JavaDoc) {
390             String JavaDoc str = (String JavaDoc) value;
391             if ("true".equals(str)) {
392                 return Boolean.TRUE;
393             }
394             else if ("false".equals(str)) {
395                 return Boolean.FALSE;
396             }
397             else if (str.length() > 0 && Character.isDigit(str.charAt(0))) {
398                 // Looks like a number... let's try.
399
try {
400                     return new Integer JavaDoc(str);
401                 }
402                 catch (NumberFormatException JavaDoc ex) {
403                     // OK, then let's keep it as a String value.
404
return str;
405                 }
406             }
407         }
408         return value;
409     }
410
411     /**
412      * Return a <code>JRExporterParameter</code> for the given parameter object,
413      * converting it from a String if necessary.
414      * @param parameter the parameter object, either a String or a JRExporterParameter
415      * @return a JRExporterParameter for the given parameter object
416      * @see #convertToExporterParameter(String)
417      */

418     protected JRExporterParameter getExporterParameter(Object JavaDoc parameter) {
419         if (parameter instanceof JRExporterParameter) {
420             return (JRExporterParameter) parameter;
421         }
422         if (parameter instanceof String JavaDoc) {
423             return convertToExporterParameter((String JavaDoc) parameter);
424         }
425         throw new IllegalArgumentException JavaDoc(
426                 "Parameter [" + parameter + "] is invalid type. Should be either String or JRExporterParameter.");
427     }
428
429     /**
430      * Convert the given fully qualified field name to a corresponding
431      * JRExporterParameter instance.
432      * @param fqFieldName the fully qualified field name, consisting
433      * of the class name followed by a dot followed by the field name
434      * (e.g. "net.sf.jasperreports.engine.export.JRHtmlExporterParameter.IMAGES_URI")
435      * @return the corresponding JRExporterParameter instance
436      */

437     protected JRExporterParameter convertToExporterParameter(String JavaDoc fqFieldName) {
438         int index = fqFieldName.lastIndexOf('.');
439         if (index == -1 || index == fqFieldName.length()) {
440             throw new IllegalArgumentException JavaDoc(
441                     "Parameter name [" + fqFieldName + "] is not a valid static field. " +
442                     "The parameter name must map to a static field such as " +
443                     "[net.sf.jasperreports.engine.export.JRHtmlExporterParameter.IMAGES_URI]");
444         }
445         String JavaDoc className = fqFieldName.substring(0, index);
446         String JavaDoc fieldName = fqFieldName.substring(index + 1);
447
448         try {
449             Class JavaDoc cls = ClassUtils.forName(className);
450             Field JavaDoc field = cls.getField(fieldName);
451
452             if (JRExporterParameter.class.isAssignableFrom(field.getType())) {
453                 try {
454                     return (JRExporterParameter) field.get(null);
455                 }
456                 catch (IllegalAccessException JavaDoc ex) {
457                     throw new IllegalArgumentException JavaDoc(
458                             "Unable to access field [" + fieldName + "] of class [" + className + "]. " +
459                             "Check that it is static and accessible.");
460                 }
461             }
462             else {
463                 throw new IllegalArgumentException JavaDoc("Field [" + fieldName + "] on class [" + className +
464                         "] is not assignable from JRExporterParameter - check the type of this field.");
465             }
466         }
467         catch (ClassNotFoundException JavaDoc ex) {
468             throw new IllegalArgumentException JavaDoc(
469                     "Class [" + className + "] in key [" + fqFieldName + "] could not be found.");
470         }
471         catch (NoSuchFieldException JavaDoc ex) {
472             throw new IllegalArgumentException JavaDoc("Field [" + fieldName + "] in key [" + fqFieldName +
473                     "] could not be found on class [" + className + "].");
474         }
475     }
476
477     /**
478      * Loads a <code>JasperReport</code> from the specified <code>Resource</code>. If
479      * the <code>Resource</code> points to an uncompiled report design file then the
480      * report file is compiled dynamically and loaded into memory.
481      * @param resource the <code>Resource</code> containing the report definition or design
482      * @return a <code>JasperReport</code> instance
483      */

484     private JasperReport loadReport(Resource resource) throws ApplicationContextException {
485         try {
486             String JavaDoc fileName = resource.getFilename();
487             if (fileName.endsWith(".jasper")) {
488                 // Load pre-compiled report.
489
if (logger.isInfoEnabled()) {
490                     logger.info("Loading pre-compiled Jasper Report from " + resource);
491                 }
492                 return (JasperReport) JRLoader.loadObject(resource.getInputStream());
493             }
494             else if (fileName.endsWith(".jrxml")) {
495                 // Compile report on-the-fly.
496
if (logger.isInfoEnabled()) {
497                     logger.info("Compiling Jasper Report loaded from " + resource);
498                 }
499                 JasperDesign design = JRXmlLoader.load(resource.getInputStream());
500                 return getReportCompiler().compileReport(design);
501             }
502             else {
503                 throw new IllegalArgumentException JavaDoc(
504                         "Report URL [" + getUrl() + "] must end in either .jasper or .jrxml");
505             }
506         }
507         catch (IOException JavaDoc ex) {
508             throw new ApplicationContextException(
509                     "Could not load JasperReports report for URL [" + getUrl() + "]", ex);
510         }
511         catch (JRException ex) {
512             throw new ApplicationContextException(
513                     "Could not parse JasperReports report for URL [" + getUrl() + "]", ex);
514         }
515     }
516
517
518     /**
519      * Finds the report data to use for rendering the report and then invokes the
520      * <code>renderReport</code> method that should be implemented by the subclass.
521      * @param model the model map, as passed in for view rendering. Must contain
522      * a report data value that can be converted to a <code>JRDataSource</code>,
523      * acccording to the <code>getReportData</code> method.
524      * @see #getReportData
525      */

526     protected void renderMergedOutputModel(Map JavaDoc model, HttpServletRequest JavaDoc request, HttpServletResponse JavaDoc response)
527             throws Exception JavaDoc {
528
529         if (this.subReports != null) {
530             // Expose sub-reports as model attributes.
531
model.putAll(this.subReports);
532
533             // Transform any collections etc into JRDataSources for sub reports.
534
if (this.subReportDataKeys != null) {
535                 for (int i = 0; i < this.subReportDataKeys.length; i++) {
536                     String JavaDoc key = this.subReportDataKeys[i];
537                     model.put(key, convertReportData(model.get(key)));
538                 }
539             }
540         }
541
542         // Expose Spring-managed Locale and MessageSource.
543
exposeLocalizationContext(model, request);
544
545         // Fill the report.
546
JasperPrint filledReport = fillReport(model);
547         postProcessReport(filledReport, model);
548
549         // Prepare response and render report.
550
response.reset();
551         populateHeaders(response);
552         renderReport(filledReport, model, response);
553     }
554
555     /**
556      * Expose current Spring-managed Locale and MessageSource to JasperReports i18n
557      * ($R expressions etc). The MessageSource should only be exposed as JasperReports
558      * resource bundle if no such bundle is defined in the report itself.
559      * <p>Default implementation exposes the Spring RequestContext Locale and a
560      * MessageSourceResourceBundle adapter for the Spring ApplicationContext,
561      * analogous to the <code>JstlUtils.exposeLocalizationContext</code> method.
562      * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
563      * @see org.springframework.context.support.MessageSourceResourceBundle
564      * @see #getApplicationContext()
565      * @see net.sf.jasperreports.engine.JRParameter#REPORT_LOCALE
566      * @see net.sf.jasperreports.engine.JRParameter#REPORT_RESOURCE_BUNDLE
567      * @see org.springframework.web.servlet.support.JstlUtils#exposeLocalizationContext
568      */

569     protected void exposeLocalizationContext(Map JavaDoc model, HttpServletRequest JavaDoc request) {
570         Locale JavaDoc locale = RequestContextUtils.getLocale(request);
571         model.put(JRParameter.REPORT_LOCALE, locale);
572         if (this.report.getResourceBundle() == null) {
573             ResourceBundle JavaDoc bundle = new MessageSourceResourceBundle(getApplicationContext(), locale);
574             model.put(JRParameter.REPORT_RESOURCE_BUNDLE, bundle);
575         }
576     }
577
578     /**
579      * Creates a populated <code>JasperPrint</code> instance from the configured
580      * <code>JasperReport</code> instance. By default, will use any <code>JRDataSource</code>
581      * instance (or wrappable <code>Object</code>) that can be located using
582      * <code>getReportData(Map)</code>. If no <code>JRDataSource</code> can be found, will use a
583      * <code>Connection</code> obtained from the configured <code>javax.sql.DataSource</code>.
584      * @param model the model for this request
585      * @throws IllegalArgumentException if no <code>JRDataSource</code> can be found
586      * and no <code>javax.sql.DataSource</code> is supplied
587      * @throws SQLException if there is an error when populating the report using
588      * the <code>javax.sql.DataSource</code>
589      * @throws JRException if there is an error when populating the report using
590      * a <code>JRDataSource</code>
591      * @return the populated <code>JasperPrint</code> instance
592      * @see #getReportData
593      * @see #setJdbcDataSource
594      */

595     protected JasperPrint fillReport(Map JavaDoc model) throws IllegalArgumentException JavaDoc, SQLException JavaDoc, JRException {
596         // Determine JRDataSource for main report.
597
JRDataSource jrDataSource = getReportData(model);
598
599         if (jrDataSource != null) {
600             // Use the JasperReports JRDataSource.
601
if (logger.isDebugEnabled()) {
602                 logger.debug("Filling report with JRDataSource [" + jrDataSource + "].");
603             }
604             return JasperFillManager.fillReport(this.report, model, jrDataSource);
605         }
606
607         else {
608             if (this.jdbcDataSource == null) {
609                 this.jdbcDataSource = (DataSource JavaDoc) CollectionUtils.findValueOfType(model.values(), DataSource JavaDoc.class);
610                 if (this.jdbcDataSource == null) {
611                     throw new IllegalArgumentException JavaDoc(
612                             "No report data source found in model, " +
613                             "and no [javax.sql.DataSource] specified in configuration or in model");
614                 }
615             }
616
617             // Use the JDBC DataSource.
618
if (logger.isDebugEnabled()) {
619                 logger.debug("Filling report with JDBC DataSource [" + this.jdbcDataSource + "].");
620             }
621             Connection JavaDoc con = this.jdbcDataSource.getConnection();
622             try {
623                 return JasperFillManager.fillReport(this.report, model, con);
624             }
625             finally {
626                 try {
627                     con.close();
628                 }
629                 catch (SQLException JavaDoc ex) {
630                     logger.warn("Could not close JDBC Connection", ex);
631                 }
632             }
633         }
634     }
635
636     /**
637      * Populates the headers in the <code>HttpServletResponse</code> with the
638      * headers supplied by the user.
639      */

640     private void populateHeaders(HttpServletResponse JavaDoc response) {
641         // Apply the headers to the response.
642
for (Enumeration JavaDoc en = this.headers.propertyNames(); en.hasMoreElements();) {
643             String JavaDoc key = (String JavaDoc) en.nextElement();
644             response.addHeader(key, this.headers.getProperty(key));
645         }
646     }
647
648     /**
649      * Find an instance of <code>JRDataSource</code> in the given model map or create an
650      * appropriate JRDataSource for passed-in report data.
651      * <p>The default implementation checks for a model object under the
652      * specified "reportDataKey" first, then falls back to looking for a value
653      * of type <code>JRDataSource</code>, <code>java.util.Collection</code>,
654      * object array (in that order).
655      * @param model the model map, as passed in for view rendering
656      * @return the <code>JRDataSource</code> or <code>null</code> if the data source is not found
657      * @see #setReportDataKey
658      * @see #convertReportData
659      * @see #getReportDataTypes
660      */

661     protected JRDataSource getReportData(Map JavaDoc model) {
662         // Try model attribute with specified name.
663
if (this.reportDataKey != null) {
664             Object JavaDoc value = model.get(this.reportDataKey);
665             return convertReportData(value);
666         }
667
668         // Try to find matching attribute, of given prioritized types.
669
Object JavaDoc value = CollectionUtils.findValueOfType(model.values(), getReportDataTypes());
670
671         if (value != null) {
672             return convertReportData(value);
673         }
674
675         return null;
676     }
677
678     /**
679      * Convert the given report data value to a <code>JRDataSource</code>.
680      * <p>The default implementation delegates to <code>JasperReportUtils</code> unless
681      * the report data value is an instance of <code>JRDataSourceProvider</code>.
682      * A <code>JRDataSource</code>, <code>JRDataSourceProvider</code>,
683      * <code>java.util.Collection</code> or object array is detected.
684      * <code>JRDataSource</code>s are returned as is, whilst <code>JRDataSourceProvider</code>s
685      * are used to create an instance of <code>JRDataSource</code> which is then returned.
686      * The latter two are converted to <code>JRBeanCollectionDataSource</code> or
687      * <code>JRBeanArrayDataSource</code>, respectively.
688      * @param value the report data value to convert
689      * @return the JRDataSource
690      * @throws IllegalArgumentException if the value could not be converted
691      * @see org.springframework.ui.jasperreports.JasperReportsUtils#convertReportData
692      * @see net.sf.jasperreports.engine.JRDataSource
693      * @see net.sf.jasperreports.engine.JRDataSourceProvider
694      * @see net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
695      * @see net.sf.jasperreports.engine.data.JRBeanArrayDataSource
696      */

697     protected JRDataSource convertReportData(Object JavaDoc value) throws IllegalArgumentException JavaDoc {
698         if (value instanceof JRDataSourceProvider) {
699             try {
700                 return ((JRDataSourceProvider) value).create(this.report);
701             }
702             catch (JRException ex) {
703                 throw new IllegalArgumentException JavaDoc("Supplied JRDataSourceProvider is invalid: " + ex);
704             }
705         }
706         else {
707             return JasperReportsUtils.convertReportData(value);
708         }
709     }
710
711     /**
712      * Return the value types that can be converted to a <code>JRDataSource</code>,
713      * in prioritized order. Should only return types that the
714      * <code>convertReportData</code> method is actually able to convert.
715      * <p>Default value types are: <code>JRDataSource</code>,
716      * <code>JRDataSourceProvider</code> <code>java.util.Collection</code>
717      * and <code>Object</code> array.
718      * @return the value types in prioritized order
719      * @see #convertReportData
720      */

721     protected Class JavaDoc[] getReportDataTypes() {
722         return new Class JavaDoc[] {JRDataSource.class, JRDataSourceProvider.class, Collection JavaDoc.class, Object JavaDoc[].class};
723     }
724
725     /**
726      * Allows sub-classes to get access to the <code>JasperReport</code> instance
727      * loaded by Spring.
728      * @return an instance of <code>JasperReport</code>
729      */

730     protected JasperReport getReport() {
731         return this.report;
732     }
733
734
735     /**
736      * Template method to be overridden for custom post-processing of the
737      * populated report. Invoked after filling but before rendering.
738      * <p>The default implementation is empty.
739      * @param populatedReport the populated <code>JasperPrint</code>
740      * @param model the map containing report parameters
741      * @throws Exception if post-processing failed
742      */

743     protected void postProcessReport(JasperPrint populatedReport, Map JavaDoc model) throws Exception JavaDoc {
744     }
745
746     /**
747      * Subclasses should implement this method to perform the actual rendering process.
748      * <p>Note that the content type has not been set yet: Implementors should build
749      * a content type String and set it via <code>response.setContentType</code>.
750      * If necessary, this can include a charset clause for a specific encoding.
751      * The latter will only be necessary for textual output onto a Writer, and only
752      * in case of the encoding being specified in the JasperReports exporter parameters.
753      * <p><b>WARNING:</b> Implementors should not use <code>response.setCharacterEncoding</code>
754      * unless they are willing to depend on Servlet API 2.4 or higher. Prefer a
755      * concatenated content type String with a charset clause instead.
756      * @param populatedReport the populated <code>JasperPrint</code> to render
757      * @param model the map containing report parameters
758      * @param response the HTTP response the report should be rendered to
759      * @throws Exception if rendering failed
760      * @see #getContentType()
761      * @see javax.servlet.ServletResponse#setContentType
762      * @see javax.servlet.ServletResponse#setCharacterEncoding
763      */

764     protected abstract void renderReport(JasperPrint populatedReport, Map JavaDoc model, HttpServletResponse JavaDoc response)
765             throws Exception JavaDoc;
766
767 }
768
Popular Tags