Package org.springframework.web.servlet.view.jasperreports

Source Code of org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsView

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

package org.springframework.web.servlet.view.jasperreports;

import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.ResourceBundle;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRDataSourceProvider;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.design.JRCompiler;
import net.sf.jasperreports.engine.design.JRDefaultCompiler;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.engine.xml.JRXmlLoader;

import org.springframework.context.ApplicationContextException;
import org.springframework.context.support.MessageSourceResourceBundle;
import org.springframework.core.io.Resource;
import org.springframework.ui.jasperreports.JasperReportsUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.web.servlet.view.AbstractUrlBasedView;

/**
* Base class for all JasperReports views. Applies on-the-fly compilation
* of report designs as required and coordinates the rendering process.
* The resource path of the main report needs to be specified as <code>url</code>.
*
* <p>This class is responsible for getting report data from the model that has
* been provided to the view. The default implementation checks for a model object
* under the specified <code>reportDataKey</code> first, then falls back to looking
* for a value of type <code>JRDataSource</code>, <code>java.util.Collection</code>,
* object array (in that order).
*
* <p>If no <code>JRDataSource</code> can be found in the model, then reports will
* be filled using the configured <code>javax.sql.DataSource</code> if any. If neither
* a <code>JRDataSource</code> or <code>javax.sql.DataSource</code> is available then
* an <code>IllegalArgumentException</code> is raised.
*
* <p>Provides support for sub-reports through the <code>subReportUrls</code> and
* <code>subReportDataKeys</code> properties.
*
* <p>When using sub-reports, the master report should be configured using the
* <code>url</code> property and the sub-reports files should be configured using
* the <code>subReportUrls</code> property. Each entry in the <code>subReportUrls</code>
* Map corresponds to an individual sub-report. The key of an entry must match up
* to a sub-report parameter in your report file of type
* <code>net.sf.jasperreports.engine.JasperReport</code>,
* and the value of an entry must be the URL for the sub-report file.
*
* <p>For sub-reports that require an instance of <code>JRDataSource</code>, that is,
* they don't have a hard-coded query for data retrieval, you can include the
* appropriate data in your model as would with the data source for the parent report.
* However, you must provide a List of parameter names that need to be converted to
* <code>JRDataSource</code> instances for the sub-report via the
* <code>subReportDataKeys</code> property. When using <code>JRDataSource</code>
* instances for sub-reports, you <i>must</i> specify a value for the
* <code>reportDataKey</code> property, indicating the data to use for the main report.
*
* <p>Allows for exporter parameters to be configured declatively using the
* <code>exporterParameters</code> property. This is a <code>Map</code> typed
* property where the key of an entry corresponds to the fully-qualified name
* of the static field for the <code>JRExporterParameter</code> and the value
* of an entry is the value you want to assign to the exporter parameter.
*
* <p>Response headers can be controlled via the <code>headers</code> property. Spring
* will attempt to set the correct value for the <code>Content-Diposition</code> header
* so that reports render correctly in Internet Explorer. However, you can override this
* setting through the <code>headers</code> property.
*
* @author Rob Harrop
* @author Juergen Hoeller
* @since 1.1.3
* @see #setUrl
* @see #setReportDataKey
* @see #setSubReportUrls
* @see #setSubReportDataKeys
* @see #setHeaders
* @see #setExporterParameters
* @see #setJdbcDataSource
*/
public abstract class AbstractJasperReportsView extends AbstractUrlBasedView {

  /**
   * Constant that defines "Content-Disposition" header.
   */
  protected static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";

  /**
   * The default Content-Disposition header. Used to make IE play nice.
   */
  protected static final String CONTENT_DISPOSITION_INLINE = "inline";


  /**
   * A String key used to lookup the <code>JRDataSource</code> in the model.
   */
  private String reportDataKey;

  /**
   * Stores the paths to any sub-report files used by this top-level report,
   * along with the keys they are mapped to in the top-level report file.
   */
  private Properties subReportUrls;

  /**
   * Stores the names of any data source objects that need to be converted to
   * <code>JRDataSource</code> instances and included in the report parameters
   * to be passed on to a sub-report.
   */
  private String[] subReportDataKeys;

  /**
   * Stores the headers to written with each response
   */
  private Properties headers;

  /**
   * Stores the exporter parameters passed in by the user as passed in by the user. May be keyed as
   * <code>String</code>s with the fully qualified name of the exporter parameter field.
   */
  private Map exporterParameters = new HashMap();

  /**
   * Stores the converted exporter parameters - keyed by <code>JRExporterParameter</code>.
   */
  private Map convertedExporterParameters;

  /**
   * Stores the <code>DataSource</code>, if any, used as the report data source.
   */
  private DataSource jdbcDataSource;

  /**
   * Holds the JRCompiler implementation to use for compiling reports on-the-fly.
   */
  private JRCompiler reportCompiler = JRDefaultCompiler.getInstance();

  /**
   * The <code>JasperReport</code> that is used to render the view.
   */
  private JasperReport report;

  /**
   * Holds mappings between sub-report keys and <code>JasperReport</code> objects.
   */
  private Map subReports;


  /**
   * Set the name of the model attribute that represents the report data.
   * If not specified, the model map will be searched for a matching value type.
   * <p>A <code>JRDataSource</code> will be taken as-is. For other types, conversion
   * will apply: By default, a <code>java.util.Collection</code> will be converted
   * to <code>JRBeanCollectionDataSource</code>, and an object array to
   * <code>JRBeanArrayDataSource</code>.
   * <p><b>Note:</b> If you pass in a Collection or object array in the model map
   * for use as plain report parameter, rather than as report data to extract fields
   * from, you need to specify the key for the actual report data to use, to avoid
   * mis-detection of report data by type.
   * @see #convertReportData
   * @see net.sf.jasperreports.engine.JRDataSource
   * @see net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
   * @see net.sf.jasperreports.engine.data.JRBeanArrayDataSource
   */
  public void setReportDataKey(String reportDataKey) {
    this.reportDataKey = reportDataKey;
  }

  /**
   * Specify resource paths which must be loaded as instances of
   * <code>JasperReport</code> and passed to the JasperReports engine for
   * rendering as sub-reports, under the same keys as in this mapping.
   * @param subReports mapping between model keys and resource paths
   * (Spring resource locations)
   * @see #setUrl
   * @see org.springframework.context.ApplicationContext#getResource
   */
  public void setSubReportUrls(Properties subReports) {
    this.subReportUrls = subReports;
  }

  /**
   * Set the list of names corresponding to the model parameters that will contain
   * data source objects for use in sub-reports. Spring will convert these objects
   * to instances of <code>JRDataSource</code> where applicable and will then
   * include the resulting <code>JRDataSource</code> in the parameters passed into
   * the JasperReports engine.
   * <p>The name specified in the list should correspond to an attribute in the
   * model Map, and to a sub-report data source parameter in your report file.
   * If you pass in <code>JRDataSource</code> objects as model attributes,
   * specifing this list of keys is not required.
   * <p>If you specify a list of sub-report data keys, it is required to also
   * specify a <code>reportDataKey</code> for the main report, to avoid confusion
   * between the data source objects for the various reports involved.
   * @param subReportDataKeys list of names for sub-report data source objects
   * @see #setReportDataKey
   * @see #convertReportData
   * @see net.sf.jasperreports.engine.JRDataSource
   * @see net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
   * @see net.sf.jasperreports.engine.data.JRBeanArrayDataSource
   */
  public void setSubReportDataKeys(String[] subReportDataKeys) {
    this.subReportDataKeys = subReportDataKeys;
  }

  /**
   * Specify the set of headers that are included in each of response.
   * @param headers the headers to write to each response.
   */
  public void setHeaders(Properties headers) {
    this.headers = headers;
  }

  /**
   * Set the exporter parameters that should be used when rendering a view.
   * @param parameters <code>Map</code> with the fully qualified field name
   * of the <code>JRExporterParameter</code> instance as key
   * (e.g. "net.sf.jasperreports.engine.export.JRHtmlExporterParameter.IMAGES_URI")
   * and the value you wish to assign to the parameter as value
   */
  public void setExporterParameters(Map parameters) {
    // NOTE: Removed conversion from here since configuration of parameters
    // can also happen through access to the underlying Map using
    // getExporterParameters(). Conversion now happens in initApplicationContext,
    // and subclasses use getConvertedExporterParameters() to access the converted
    // parameter Map - robh.
    this.exporterParameters = parameters;
  }

  /**
   * Return the exporter parameters that this view uses, if any.
   */
  public Map getExporterParameters() {
    return this.exporterParameters;
  }

  /**
   * Allows subclasses to retrieve the converted exporter parameters.
   */
  protected Map getConvertedExporterParameters() {
    return this.convertedExporterParameters;
  }

  /**
   * Specify the <code>javax.sql.DataSource</code> to use for reports with
   * embedded SQL statements.
   */
  public void setJdbcDataSource(DataSource jdbcDataSource) {
    this.jdbcDataSource = jdbcDataSource;
  }

  /**
   * Return the <code>javax.sql.DataSource</code> that this view uses, if any.
   */
  protected DataSource getJdbcDataSource() {
    return this.jdbcDataSource;
  }

  /**
   * Specify the JRCompiler implementation to use for compiling a ".jrxml"
   * report file on-the-fly into a report class.
   * <p>By default, a JRDefaultCompiler will be used, delegating to the
   * Eclipse JDT compiler or the Sun JDK compiler underneath.
   * @see net.sf.jasperreports.engine.design.JRDefaultCompiler
   */
  public void setReportCompiler(JRCompiler reportCompiler) {
    this.reportCompiler = (reportCompiler != null ? reportCompiler : JRDefaultCompiler.getInstance());
  }

  /**
   * Return the JRCompiler instance to use for compiling ".jrxml" report files.
   */
  protected JRCompiler getReportCompiler() {
    return this.reportCompiler;
  }


  /**
   * JasperReports views do not strictly required a 'url' value.
   * Alternatively, the {@link #getReport()} template method may be overridden.
   */
  protected boolean isUrlRequired() {
    return false;
  }

  /**
   * Checks to see that a valid report file URL is supplied in the
   * configuration. Compiles the report file is necessary.
   * <p>Subclasses can add custom initialization logic by overriding
   * the {@link #onInit} method.
   */
  protected final void initApplicationContext() throws ApplicationContextException {
    this.report = loadReport();

    // Load sub reports if required, and check data source parameters.
    if (this.subReportUrls != null) {
      if (this.subReportDataKeys != null && this.subReportDataKeys.length > 0 && this.reportDataKey == null) {
        throw new ApplicationContextException(
            "'reportDataKey' for main report is required when specifying a value for 'subReportDataKeys'");
      }
      this.subReports = new HashMap(this.subReportUrls.size());
      for (Enumeration urls = this.subReportUrls.propertyNames(); urls.hasMoreElements();) {
        String key = (String) urls.nextElement();
        String path = this.subReportUrls.getProperty(key);
        Resource resource = getApplicationContext().getResource(path);
        this.subReports.put(key, loadReport(resource));
      }
    }

    // Convert user-supplied exporterParameters.
    convertExporterParameters();

    if (this.headers == null) {
      this.headers = new Properties();
    }
    if (!this.headers.containsKey(HEADER_CONTENT_DISPOSITION)) {
      this.headers.setProperty(HEADER_CONTENT_DISPOSITION, CONTENT_DISPOSITION_INLINE);
    }

    onInit();
  }

  /**
   * Subclasses can override this to add some custom initialization logic. Called
   * by {@link #initApplicationContext()} as soon as all standard initialization logic
   * has finished executing.
   * @see #initApplicationContext()
   */
  protected void onInit() {
  }

  /**
   * Converts the exporter parameters passed in by the user which may be keyed
   * by <code>String</code>s corresponding to the fully qualified name of the
   * <code>JRExporterParameter</code> into parameters which are keyed by
   * <code>JRExporterParameter</code>.
   * @see #getExporterParameter(Object)
   */
  protected final void convertExporterParameters() {
    if (!CollectionUtils.isEmpty(this.exporterParameters)) {
      this.convertedExporterParameters = new HashMap(this.exporterParameters.size());
      for (Iterator it = this.exporterParameters.entrySet().iterator(); it.hasNext();) {
        Map.Entry entry = (Map.Entry) it.next();
        JRExporterParameter exporterParameter = getExporterParameter(entry.getKey());
        this.convertedExporterParameters.put(
            exporterParameter, convertParameterValue(exporterParameter, entry.getValue()));
      }
    }
  }

  /**
   * Convert the supplied parameter value into the actual type required by the
   * corresponding {@link JRExporterParameter}.
   * <p>The default implementation simply converts the String values "true" and
   * "false" into corresponding <code>Boolean</code> objects, and tries to convert
   * String values that start with a digit into <code>Integer</code> objects
   * (simply keeping them as String if number conversion fails).
   * @param parameter the parameter key
   * @param value the parameter value
   * @return the converted parameter value
   */
  protected Object convertParameterValue(JRExporterParameter parameter, Object value) {
    if (value instanceof String) {
      String str = (String) value;
      if ("true".equals(str)) {
        return Boolean.TRUE;
      }
      else if ("false".equals(str)) {
        return Boolean.FALSE;
      }
      else if (str.length() > 0 && Character.isDigit(str.charAt(0))) {
        // Looks like a number... let's try.
        try {
          return new Integer(str);
        }
        catch (NumberFormatException ex) {
          // OK, then let's keep it as a String value.
          return str;
        }
      }
    }
    return value;
  }

  /**
   * Return a <code>JRExporterParameter</code> for the given parameter object,
   * converting it from a String if necessary.
   * @param parameter the parameter object, either a String or a JRExporterParameter
   * @return a JRExporterParameter for the given parameter object
   * @see #convertToExporterParameter(String)
   */
  protected JRExporterParameter getExporterParameter(Object parameter) {
    if (parameter instanceof JRExporterParameter) {
      return (JRExporterParameter) parameter;
    }
    if (parameter instanceof String) {
      return convertToExporterParameter((String) parameter);
    }
    throw new IllegalArgumentException(
        "Parameter [" + parameter + "] is invalid type. Should be either String or JRExporterParameter.");
  }

  /**
   * Convert the given fully qualified field name to a corresponding
   * JRExporterParameter instance.
   * @param fqFieldName the fully qualified field name, consisting
   * of the class name followed by a dot followed by the field name
   * (e.g. "net.sf.jasperreports.engine.export.JRHtmlExporterParameter.IMAGES_URI")
   * @return the corresponding JRExporterParameter instance
   */
  protected JRExporterParameter convertToExporterParameter(String fqFieldName) {
    int index = fqFieldName.lastIndexOf('.');
    if (index == -1 || index == fqFieldName.length()) {
      throw new IllegalArgumentException(
          "Parameter name [" + fqFieldName + "] is not a valid static field. " +
          "The parameter name must map to a static field such as " +
          "[net.sf.jasperreports.engine.export.JRHtmlExporterParameter.IMAGES_URI]");
    }
    String className = fqFieldName.substring(0, index);
    String fieldName = fqFieldName.substring(index + 1);

    try {
      Class cls = ClassUtils.forName(className);
      Field field = cls.getField(fieldName);

      if (JRExporterParameter.class.isAssignableFrom(field.getType())) {
        try {
          return (JRExporterParameter) field.get(null);
        }
        catch (IllegalAccessException ex) {
          throw new IllegalArgumentException(
              "Unable to access field [" + fieldName + "] of class [" + className + "]. " +
              "Check that it is static and accessible.");
        }
      }
      else {
        throw new IllegalArgumentException("Field [" + fieldName + "] on class [" + className +
            "] is not assignable from JRExporterParameter - check the type of this field.");
      }
    }
    catch (ClassNotFoundException ex) {
      throw new IllegalArgumentException(
          "Class [" + className + "] in key [" + fqFieldName + "] could not be found.");
    }
    catch (NoSuchFieldException ex) {
      throw new IllegalArgumentException("Field [" + fieldName + "] in key [" + fqFieldName +
          "] could not be found on class [" + className + "].");
    }
  }

  /**
   * Load the main <code>JasperReport</code> from the specified <code>Resource</code>.
   * If the <code>Resource</code> points to an uncompiled report design file then the
   * report file is compiled dynamically and loaded into memory.
   * @return a <code>JasperReport</code> instance, or <code>null</code> if no main
   * report has been statically defined
   */
  protected JasperReport loadReport() {
    String url = getUrl();
    if (url == null) {
      return null;
    }
    Resource mainReport = getApplicationContext().getResource(url);
    return loadReport(mainReport);
  }

  /**
   * Loads a <code>JasperReport</code> from the specified <code>Resource</code>.
   * If the <code>Resource</code> points to an uncompiled report design file then
   * the report file is compiled dynamically and loaded into memory.
   * @param resource the <code>Resource</code> containing the report definition or design
   * @return a <code>JasperReport</code> instance
   */
  protected final JasperReport loadReport(Resource resource) {
    try {
      String fileName = resource.getFilename();
      if (fileName.endsWith(".jasper")) {
        // Load pre-compiled report.
        if (logger.isInfoEnabled()) {
          logger.info("Loading pre-compiled Jasper Report from " + resource);
        }
        return (JasperReport) JRLoader.loadObject(resource.getInputStream());
      }
      else if (fileName.endsWith(".jrxml")) {
        // Compile report on-the-fly.
        if (logger.isInfoEnabled()) {
          logger.info("Compiling Jasper Report loaded from " + resource);
        }
        JasperDesign design = JRXmlLoader.load(resource.getInputStream());
        return getReportCompiler().compileReport(design);
      }
      else {
        throw new IllegalArgumentException(
            "Report URL [" + getUrl() + "] must end in either .jasper or .jrxml");
      }
    }
    catch (IOException ex) {
      throw new ApplicationContextException(
          "Could not load JasperReports report for URL [" + getUrl() + "]", ex);
    }
    catch (JRException ex) {
      throw new ApplicationContextException(
          "Could not parse JasperReports report for URL [" + getUrl() + "]", ex);
    }
  }


  /**
   * Finds the report data to use for rendering the report and then invokes the
   * <code>renderReport</code> method that should be implemented by the subclass.
   * @param model the model map, as passed in for view rendering. Must contain
   * a report data value that can be converted to a <code>JRDataSource</code>,
   * acccording to the <code>getReportData</code> method.
   * @see #getReportData
   */
  protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response)
      throws Exception {

    if (this.subReports != null) {
      // Expose sub-reports as model attributes.
      model.putAll(this.subReports);

      // Transform any collections etc into JRDataSources for sub reports.
      if (this.subReportDataKeys != null) {
        for (int i = 0; i < this.subReportDataKeys.length; i++) {
          String key = this.subReportDataKeys[i];
          model.put(key, convertReportData(model.get(key)));
        }
      }
    }

    // Expose Spring-managed Locale and MessageSource.
    exposeLocalizationContext(model, request);

    // Fill the report.
    JasperPrint filledReport = fillReport(model);
    postProcessReport(filledReport, model);

    // Prepare response and render report.
    response.reset();
    populateHeaders(response);
    renderReport(filledReport, model, response);
  }

  /**
   * Expose current Spring-managed Locale and MessageSource to JasperReports i18n
   * ($R expressions etc). The MessageSource should only be exposed as JasperReports
   * resource bundle if no such bundle is defined in the report itself.
   * <p>Default implementation exposes the Spring RequestContext Locale and a
   * MessageSourceResourceBundle adapter for the Spring ApplicationContext,
   * analogous to the <code>JstlUtils.exposeLocalizationContext</code> method.
   * @see org.springframework.web.servlet.support.RequestContextUtils#getLocale
   * @see org.springframework.context.support.MessageSourceResourceBundle
   * @see #getApplicationContext()
   * @see net.sf.jasperreports.engine.JRParameter#REPORT_LOCALE
   * @see net.sf.jasperreports.engine.JRParameter#REPORT_RESOURCE_BUNDLE
   * @see org.springframework.web.servlet.support.JstlUtils#exposeLocalizationContext
   */
  protected void exposeLocalizationContext(Map model, HttpServletRequest request) {
    Locale locale = RequestContextUtils.getLocale(request);
    model.put(JRParameter.REPORT_LOCALE, locale);
    JasperReport report = getReport();
    if (report == null || report.getResourceBundle() == null) {
      ResourceBundle bundle = new MessageSourceResourceBundle(getApplicationContext(), locale);
      model.put(JRParameter.REPORT_RESOURCE_BUNDLE, bundle);
    }
  }

  /**
   * Create a populated <code>JasperPrint</code> instance from the configured
   * <code>JasperReport</code> instance.
   * <p>By default, thois method will use any <code>JRDataSource</code> instance
   * (or wrappable <code>Object</code>) that can be located using {@link #getReportData}.
   * If no <code>JRDataSource</code> can be found, this method will use a JDBC
   * <code>Connection</code> obtained from the configured <code>javax.sql.DataSource</code>
   * (or a DataSource attribute in the model). If no JDBC DataSource can be found
   * either, the JasperReports engine will be invoked with plain model Map,
   * assuming that the model contains parameters that identify the source
   * for report data (e.g. Hibernate or JPA queries).
   * @param model the model for this request
   * @throws IllegalArgumentException if no <code>JRDataSource</code> can be found
   * and no <code>javax.sql.DataSource</code> is supplied
   * @throws SQLException if there is an error when populating the report using
   * the <code>javax.sql.DataSource</code>
   * @throws JRException if there is an error when populating the report using
   * a <code>JRDataSource</code>
   * @return the populated <code>JasperPrint</code> instance
   * @see #getReportData
   * @see #setJdbcDataSource
   */
  protected JasperPrint fillReport(Map model) throws IllegalArgumentException, SQLException, JRException {
    // Determine main report.
    JasperReport report = getReport();
    if (report == null) {
      throw new IllegalStateException("No main report defined for 'fillReport' - " +
          "specify a 'url' on this view or override 'getReport()' or 'fillReport(Map)'");
    }

    // Determine JRDataSource for main report.
    JRDataSource jrDataSource = getReportData(model);
    if (jrDataSource != null) {
      // Use the JasperReports JRDataSource.
      if (logger.isDebugEnabled()) {
        logger.debug("Filling report with JRDataSource [" + jrDataSource + "].");
      }
      return JasperFillManager.fillReport(report, model, jrDataSource);
    }

    else {
      if (this.jdbcDataSource == null) {
        this.jdbcDataSource = (DataSource) CollectionUtils.findValueOfType(model.values(), DataSource.class);
      }

      if (this.jdbcDataSource != null) {
        // Use the JDBC DataSource.
        if (logger.isDebugEnabled()) {
          logger.debug("Filling report with JDBC DataSource [" + this.jdbcDataSource + "].");
        }
        Connection con = this.jdbcDataSource.getConnection();
        try {
          return JasperFillManager.fillReport(report, model, con);
        }
        finally {
          try {
            con.close();
          }
          catch (Throwable ex) {
            logger.debug("Could not close JDBC Connection", ex);
          }
        }
      }

      else {
        // Assume that the model contains parameters that identify
        // the source for report data (e.g. Hibernate or JPA queries).
        return JasperFillManager.fillReport(report, model);
      }
    }
  }

  /**
   * Populates the headers in the <code>HttpServletResponse</code> with the
   * headers supplied by the user.
   */
  private void populateHeaders(HttpServletResponse response) {
    // Apply the headers to the response.
    for (Enumeration en = this.headers.propertyNames(); en.hasMoreElements();) {
      String key = (String) en.nextElement();
      response.addHeader(key, this.headers.getProperty(key));
    }
  }

  /**
   * Determine the <code>JasperReport</code> to fill.
   * Called by {@link #fillReport}.
   * <p>The default implementation returns the report as statically configured
   * through the 'url' property (and loaded by {@link #loadReport()}).
   * Can be overridden in subclasses in order to dynamically obtain a
   * <code>JasperReport</code> instance. As an alternative, consider
   * overriding the {@link #fillReport} template method itself.
   * @return an instance of <code>JasperReport</code>
   */
  protected JasperReport getReport() {
    return this.report;
  }

  /**
   * Find an instance of <code>JRDataSource</code> in the given model map or create an
   * appropriate JRDataSource for passed-in report data.
   * <p>The default implementation checks for a model object under the
   * specified "reportDataKey" first, then falls back to looking for a value
   * of type <code>JRDataSource</code>, <code>java.util.Collection</code>,
   * object array (in that order).
   * @param model the model map, as passed in for view rendering
   * @return the <code>JRDataSource</code> or <code>null</code> if the data source is not found
   * @see #setReportDataKey
   * @see #convertReportData
   * @see #getReportDataTypes
   */
  protected JRDataSource getReportData(Map model) {
    // Try model attribute with specified name.
    if (this.reportDataKey != null) {
      Object value = model.get(this.reportDataKey);
      return convertReportData(value);
    }

    // Try to find matching attribute, of given prioritized types.
    Object value = CollectionUtils.findValueOfType(model.values(), getReportDataTypes());

    if (value != null) {
      return convertReportData(value);
    }

    return null;
  }

  /**
   * Convert the given report data value to a <code>JRDataSource</code>.
   * <p>The default implementation delegates to <code>JasperReportUtils</code> unless
   * the report data value is an instance of <code>JRDataSourceProvider</code>.
   * A <code>JRDataSource</code>, <code>JRDataSourceProvider</code>,
   * <code>java.util.Collection</code> or object array is detected.
   * <code>JRDataSource</code>s are returned as is, whilst <code>JRDataSourceProvider</code>s
   * are used to create an instance of <code>JRDataSource</code> which is then returned.
   * The latter two are converted to <code>JRBeanCollectionDataSource</code> or
   * <code>JRBeanArrayDataSource</code>, respectively.
   * @param value the report data value to convert
   * @return the JRDataSource
   * @throws IllegalArgumentException if the value could not be converted
   * @see org.springframework.ui.jasperreports.JasperReportsUtils#convertReportData
   * @see net.sf.jasperreports.engine.JRDataSource
   * @see net.sf.jasperreports.engine.JRDataSourceProvider
   * @see net.sf.jasperreports.engine.data.JRBeanCollectionDataSource
   * @see net.sf.jasperreports.engine.data.JRBeanArrayDataSource
   */
  protected JRDataSource convertReportData(Object value) throws IllegalArgumentException {
    if (value instanceof JRDataSourceProvider) {
      try {
        JasperReport report = getReport();
        if (report == null) {
          throw new IllegalStateException("No main report defined for JRDataSourceProvider - " +
              "specify a 'url' on this view or override 'getReport()'");
        }
        return ((JRDataSourceProvider) value).create(report);
      }
      catch (JRException ex) {
        throw new IllegalArgumentException("Supplied JRDataSourceProvider is invalid: " + ex);
      }
    }
    else {
      return JasperReportsUtils.convertReportData(value);
    }
  }

  /**
   * Return the value types that can be converted to a <code>JRDataSource</code>,
   * in prioritized order. Should only return types that the
   * {@link #convertReportData} method is actually able to convert.
   * <p>Default value types are: <code>JRDataSource</code>,
   * <code>JRDataSourceProvider</code> <code>java.util.Collection</code>
   * and <code>Object</code> array.
   * @return the value types in prioritized order
   */
  protected Class[] getReportDataTypes() {
    return new Class[] {JRDataSource.class, JRDataSourceProvider.class, Collection.class, Object[].class};
  }


  /**
   * Template method to be overridden for custom post-processing of the
   * populated report. Invoked after filling but before rendering.
   * <p>The default implementation is empty.
   * @param populatedReport the populated <code>JasperPrint</code>
   * @param model the map containing report parameters
   * @throws Exception if post-processing failed
   */
  protected void postProcessReport(JasperPrint populatedReport, Map model) throws Exception {
  }

  /**
   * Subclasses should implement this method to perform the actual rendering process.
   * <p>Note that the content type has not been set yet: Implementors should build
   * a content type String and set it via <code>response.setContentType</code>.
   * If necessary, this can include a charset clause for a specific encoding.
   * The latter will only be necessary for textual output onto a Writer, and only
   * in case of the encoding being specified in the JasperReports exporter parameters.
   * <p><b>WARNING:</b> Implementors should not use <code>response.setCharacterEncoding</code>
   * unless they are willing to depend on Servlet API 2.4 or higher. Prefer a
   * concatenated content type String with a charset clause instead.
   * @param populatedReport the populated <code>JasperPrint</code> to render
   * @param model the map containing report parameters
   * @param response the HTTP response the report should be rendered to
   * @throws Exception if rendering failed
   * @see #getContentType()
   * @see javax.servlet.ServletResponse#setContentType
   * @see javax.servlet.ServletResponse#setCharacterEncoding
   */
  protected abstract void renderReport(JasperPrint populatedReport, Map model, HttpServletResponse response)
      throws Exception;

}
TOP

Related Classes of org.springframework.web.servlet.view.jasperreports.AbstractJasperReportsView

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.