/**
 * 
 */
package it.mice.voila.runtime.jasper;

import it.mice.voila.runtime.springmvc.util.SpringMvcUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

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.JRExporter;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRTemplate;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.design.JasperDesign;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.JRHtmlExporter;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.export.JRPdfExporterParameter;
import net.sf.jasperreports.engine.export.JRRtfExporter;
import net.sf.jasperreports.engine.export.JRXlsExporter;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.engine.xml.JRXmlLoader;
import net.sf.jasperreports.engine.xml.JRXmlTemplateLoader;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ApplicationObjectSupport;
import org.springframework.context.support.MessageSourceResourceBundle;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.ui.jasperreports.JasperReportsUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.theme.SessionThemeResolver;

/**
 *
 */
public class JasperReportDefinition extends ApplicationObjectSupport {
	/** Logger that is available to subclasses */
	protected final Log logger = LogFactory.getLog(getClass());

	/**
	 * Default value used for format key: "format"
	 */
	public static final String DEFAULT_FORMAT_KEY = "format";

	/**
	 * The key of the model parameter that holds the format key.
	 */
	private String formatKey = DEFAULT_FORMAT_KEY;

	@Autowired(required=false)
	private MessageSource messageSource = null;
	
	/**
	 * Stores the format mappings, with the format discriminator
	 * as key and the corresponding view class as value.
	 */
	private Map<String, Class<? extends JRExporter>> formatMappings;

	private boolean debug = false;

	/**
	 * url
	 */
	private String url;
	/**
	 * 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 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 templateUrls;

	/**
	 * 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 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<Object, Object>();

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

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

	/**
	 * 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<String, JasperReport> subReports;

	/**
	 * Holds mappings between sub-report keys and <code>JasperReport</code> objects.
	 */
	private Map<String, JRTemplate> templates;

	/**
	 * The file that contains the default icc profile - used for pdfa compliance
	 */
	private static String DEFAULT_ICC_PROFILE="AdobeRGB1998.icc";
	
	/**
	 * Costruttore di classe.
	 */
	public JasperReportDefinition() {
		Map<String, Class<? extends JRExporter>> mappings = new HashMap<String, Class<? extends JRExporter>>(5);
		mappings.put("csv", JRCsvExporter.class);
		mappings.put("html", JRHtmlExporter.class);
		mappings.put("pdf", JRPdfExporter.class);
		mappings.put("xls", JRXlsExporter.class);
		mappings.put("rtf", JRRtfExporter.class);
		setFormatMappings(mappings);
	}

	/**
	 * Getter per la proprietà messageSource.
	 * @return la proprietà messageSource
	 */
	public MessageSource getMessageSource() {
		return messageSource;
	}


	/**
	 * Setter per la proprietà messageSource.
	 * @param messageSource la proprietà messageSource da settare
	 */
	public void setMessageSource(MessageSource messageSource) {
		this.messageSource = messageSource;
	}


	/**
	 * Getter per la proprietà debug.
	 * @return la proprietà debug
	 */
	public boolean isDebug() {
		return debug;
	}

	/**
	 * Setter per la proprietà debug.
	 * @param debug la proprietà debug da settare
	 */
	public void setDebug(boolean debug) {
		this.debug = debug;
	}

	/**
	 * 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;
	}

	/**
	 * @param templateUrls the templateUrls to set
	 */
	public void setTemplateUrls(Properties templateUrls) {
		this.templateUrls = templateUrls;
	}

	/**
	 * 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;
	}

	/**
	 * 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) {
		this.exporterParameters = parameters;
	}

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

	/**
	 * Allows subclasses to populate the converted exporter parameters.
	 */
	public void setConvertedExporterParameters(Map<JRExporterParameter, Object> convertedExporterParameters) {
		this.convertedExporterParameters = convertedExporterParameters;
	}

	/**
	 * Allows subclasses to retrieve the converted exporter parameters.
	 */
	public Map<JRExporterParameter, Object> 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;
	}


	/**
	 * Set the URL of the resource that this view wraps.
	 * The URL must be appropriate for the concrete View implementation.
	 */
	public void setUrl(String url) {
		this.url = url;
	}

	/**
	 * Return the URL of the resource that this view wraps.
	 */
	public String getUrl() {
		return this.url;
	}

	/**
	 * Getter per la proprietà formatKey.
	 * @return la proprietà formatKey
	 */
	public String getFormatKey() {
		return formatKey;
	}

	/**
	 * Setter per la proprietà formatKey.
	 * @param formatKey la proprietà formatKey da settare
	 */
	public void setFormatKey(String formatKey) {
		this.formatKey = formatKey;
	}

	/**
	 * Getter per la proprietà formatMappings.
	 * @return la proprietà formatMappings
	 */
	public Map<String, Class<? extends JRExporter>> getFormatMappings() {
		return formatMappings;
	}

	/**
	 * Setter per la proprietà formatMappings.
	 * @param mappings la proprietà formatMappings da settare
	 */
	public void setFormatMappings(Map<String, Class<? extends JRExporter>> mappings) {
		this.formatMappings = mappings;
	}

	/**
	 * Getter per la proprietà subReports.
	 * @return la proprietà subReports
	 */
	public Map<String, JasperReport> getSubReports() {
		return subReports;
	}

	/**
	 * Setter per la proprietà subReports.
	 * @param subReports la proprietà subReports da settare
	 */
	public void setSubReports(Map<String, JasperReport> subReports) {
		this.subReports = subReports;
	}

	/**
	 * Getter per la proprietà reportDataKey.
	 * @return la proprietà reportDataKey
	 */
	public String getReportDataKey() {
		return reportDataKey;
	}

	/**
	 * Getter per la proprietà subReportUrls.
	 * @return la proprietà subReportUrls
	 */
	public Properties getSubReportUrls() {
		return subReportUrls;
	}

	/**
	 * Getter per la proprietà subReportDataKeys.
	 * @return la proprietà subReportDataKeys
	 */
	public String[] getSubReportDataKeys() {
		return subReportDataKeys;
	}

	/**
	 * 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;
	}

	/**
	 * Setter per la proprietà report.
	 * @param report la proprietà report da settare
	 */
	public void setReport(JasperReport report) {
		this.report = report;
	}
	
	
	
	
	
	
	
	
	
	
	
	
	

	/**
	 * Finds the report data to use for rendering the report and then invokes the
	 * {@link #renderReport} 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 rules of the {@link #fillReport} method.
	 * @param output 
	 * @throws Exception 
	 */
	public void renderToOutputStream(
			Map<String, Object> model, OutputStream output) {

		renderToOutputStream(model, output, null, this.messageSource);
	}

	/**
	 * Finds the report data to use for rendering the report and then invokes the
	 * {@link #renderReport} 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 rules of the {@link #fillReport} method.
	 * @param output 
	 * @param locale 
	 * @param aMessageSource 
	 * @throws Exception 
	 */
	public void renderToOutputStream(
			Map<String, Object> model, OutputStream output, Locale locale, MessageSource aMessageSource) {

		try {
			// Fill the report.
			JasperPrint filledReport = preparePrintReport(model, locale, aMessageSource);
			// Prepare response and render report.
			renderReport(filledReport, model, output);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public JasperPrint preparePrintReport(Map<String, Object> model, Locale locale, MessageSource aMessageSource) {
		if (isDebug()) {
			initContext();
		}
		if (this.subReports != null) {
			// Expose sub-reports as model attributes.
			model.put("SubReports", this.subReports);
			model.putAll(this.subReports);
			// Transform any collections etc into JRDataSources for sub reports.
			if (this.subReportDataKeys != null) {
				for (String key : this.subReportDataKeys) {
					model.put(key, convertReportData(model.get(key)));
				}
			}
		}

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

		// Fill the report.
		JasperPrint filledReport;
		try {
			filledReport = fillReport(model);
			postProcessReport(filledReport, model);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		
		return filledReport;
	}
	
	/**
	 * 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>The 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<String, Object> model, Locale locale, MessageSource aMessageSource) {
		if (locale != null && !model.containsKey(JRParameter.REPORT_LOCALE)) {
			model.put(JRParameter.REPORT_LOCALE, locale);
		}
		
		if (aMessageSource == null) {
			return;
		}
		
		JasperReport report = getReport();
		if ((report == null || report.getResourceBundle() == null) &&
				!model.containsKey(JRParameter.REPORT_RESOURCE_BUNDLE)) {
			model.put(JRParameter.REPORT_RESOURCE_BUNDLE,
					new MessageSourceResourceBundle(aMessageSource, locale));
		}
		
		if (templates != null && templates.size() > 0) {
			if (SpringMvcUtil.getSession() != null && SpringMvcUtil.getSession().getAttribute(SessionThemeResolver.THEME_SESSION_ATTRIBUTE_NAME) != null) {
				String themeName = (String)SpringMvcUtil.getSession().getAttribute(SessionThemeResolver.THEME_SESSION_ATTRIBUTE_NAME);
				if (templates.containsKey(themeName)) {
					Collection<JRTemplate> template = new ArrayList<JRTemplate>();
					template.add(templates.get(themeName));
					model.put(JRParameter.REPORT_TEMPLATES, template);
				}
			}
		}
	}

	/**
	 * Create a populated <code>JasperPrint</code> instance from the configured
	 * <code>JasperReport</code> instance.
	 * <p>By default, this method will use any <code>JRDataSource</code> instance
	 * (or wrappable <code>Object</code>) that can be located using {@link #setReportDataKey},
	 * a lookup for type <code>JRDataSource</code> in the model Map, or a special value
	 * retrieved via {@link #getReportData}.
	 * <p>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<String, Object> model) throws Exception {
		// 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)'");
		}

		JRDataSource jrDataSource = null;
		DataSource jdbcDataSourceToUse = null;

		// Try model attribute with specified name.
		if (this.reportDataKey != null) {
			Object reportDataValue = model.get(this.reportDataKey);
			if (reportDataValue instanceof DataSource) {
				jdbcDataSourceToUse = (DataSource) reportDataValue;
			}
			else {
				jrDataSource = convertReportData(reportDataValue);
			}
		}

		Collection values = model.values();
		if (jrDataSource == null) {
			jrDataSource = CollectionUtils.findValueOfType(values, JRDataSource.class);
		}
		if (jrDataSource == null) {
			JRDataSourceProvider provider = CollectionUtils.findValueOfType(values, JRDataSourceProvider.class);
			if (provider != null) {
				jrDataSource = createReport(provider);
			}
		}
		
		// Determine JRDataSource for main report.
		if (jrDataSource == null) {
			jrDataSource = getReportData(model);
		}
		
		if (jdbcDataSourceToUse == null) {
			jdbcDataSourceToUse = CollectionUtils.findValueOfType(values, DataSource.class);
		}
		if (jdbcDataSourceToUse == null) {
			jdbcDataSourceToUse = this.jdbcDataSource;
		}
		return doFillReport(report, model, jdbcDataSourceToUse, jrDataSource);
	}

	/**
	 * Fill the given report using the given JDBC DataSource and model.
	 */
	private JasperPrint doFillReport(JasperReport report, Map<String, Object> model, DataSource jdbcDataSourceToUse, JRDataSource jrDataSource) throws Exception {
		Connection con = null;
		try {
			if (jrDataSource != null) {
				if (jdbcDataSourceToUse != null) {
					// Use the JasperReports JRDataSource but put DataSource connection as parameter.
					if (logger.isDebugEnabled()) {
						logger.debug("Filling report with JRDataSource [" + jrDataSource + "] and put DataSource connection as REPORT_CONNECTION parameter");
					}
					con = jdbcDataSourceToUse.getConnection();
					model.put("REPORT_CONNECTION", con);
				} else {
					// Use the JasperReports JRDataSource.
					if (logger.isDebugEnabled()) {
						logger.debug("Filling report with JRDataSource [" + jrDataSource + "]");
					}
				}
				return JasperFillManager.fillReport(report, model, jrDataSource);
			}
			if (jdbcDataSourceToUse != null) {
				// Use the JDBC DataSource.
				if (logger.isDebugEnabled()) {
					logger.debug("Filling report using JDBC DataSource [" + jdbcDataSourceToUse + "]");
				}
				con = jdbcDataSourceToUse.getConnection();
				return JasperFillManager.fillReport(report, model, con);
			}
			// Assume that the model contains parameters that identify
			// the source for report data (e.g. Hibernate or JPA queries).
			if (logger.isDebugEnabled()) {
				logger.debug("Filling report with plain model");
			}
			return JasperFillManager.fillReport(report, model);
		}
		finally {
			try {
				if (con != null) {
					con.close();
				}
			}
			catch (Exception ex) {
				logger.debug("Could not close JDBC Connection", ex);
			}
		}
	}

	/**
	 * Create an appropriate <code>JRDataSource</code> for passed-in report data.
	 * Called by {@link #fillReport} when its own lookup steps were not successful.
	 * <p>The default implementation looks for a value of type <code>java.util.Collection</code>
	 * or object array (in that order). Can be overridden in subclasses.
	 * @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 #getReportDataTypes
	 * @see #convertReportData
	 */
	protected JRDataSource getReportData(Map<String, Object> model) {
		// Try to find matching attribute, of given prioritized types.
		Object value = CollectionUtils.findValueOfType(model.values(), getReportDataTypes());
		return (value != null ? convertReportData(value) : 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) {
			return createReport((JRDataSourceProvider) value);
		}
		return JasperReportsUtils.convertReportData(value);
	}

	/**
	 * Create a report using the given provider.
	 * @param provider the JRDataSourceProvider to use
	 * @return the created report
	 */
	protected JRDataSource createReport(JRDataSourceProvider provider) {
		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 provider.create(report);
		}
		catch (JRException ex) {
			throw new IllegalArgumentException("Supplied JRDataSourceProvider is invalid", ex);
		}
	}

	/**
	 * 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>java.util.Collection</code> and <code>Object</code> array.
	 * @return the value types in prioritized order
	 */
	protected Class[] getReportDataTypes() {
		return new 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<String, Object> model) throws Exception {
	}

	/**
	 * Locates the format key in the model using the configured discriminator key and uses this
	 * key to lookup the appropriate view class from the mappings. The rendering of the
	 * report is then delegated to an instance of that view class.
	 */
	protected void renderReport(
			JasperPrint populatedReport, Map<String, Object> model, OutputStream outputStream) throws Exception {

		String format = getFormat(model);
		if (format == null) {
			throw new IllegalArgumentException("No format format found in model");
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Rendering report using format mapping key [" + format + "]");
		}

		Class<? extends JRExporter> exporterClass = this.formatMappings.get(format);
		if (exporterClass == null) {
			throw new IllegalArgumentException("Format discriminator [" + format + "] is not a configured mapping");
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Rendering report using view class [" + exporterClass.getName() + "]");
		}

		JRExporter exporter = BeanUtils.instantiateClass(exporterClass);
		// Can skip most initialization since all relevant URL processing
		// has been done - just need to convert parameters on the sub view.
		if (getConvertedExporterParameters() != null) {
			exporter.setParameters(getConvertedExporterParameters());
		}

		// Prepare response and render report.
		renderReport(populatedReport, model, outputStream, exporter);
	}

	public String getFormat(Map<String, Object> model) {
		String format = (String) model.get(this.formatKey);
		return format;
	}
	
	/**
	 * renderReport.
	 * @param populatedReport
	 * @param model
	 * @param outputStream
	 * @param exporter
	 * @throws JRException
	 */
	protected void renderReport(JasperPrint populatedReport, Map<String, Object> model, OutputStream outputStream, JRExporter exporter) throws JRException {
		exporter.setParameter(JRExporterParameter.JASPER_PRINT, populatedReport);
		exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outputStream);
		exporter.exportReport();
	}
	
	/**
	 * 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.
	 */
	@Override
	protected final void initApplicationContext() throws ApplicationContextException {
		if (!isDebug()) {
			initContext();
		} else {
			logger.warn("ATTENZIONE ! Report definition " + getUrl() + " in debug mode; questo significa che i reports verranno compilati ad ogni richiesta. Vivamente sconsigliato per gli ambienti di esercizio !");
		}
	}

	private void initContext() {
		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<String, JasperReport>(this.subReportUrls.size());
			for (Enumeration urls = this.subReportUrls.propertyNames(); urls.hasMoreElements();) {
				String key = (String) urls.nextElement();
				String path = this.subReportUrls.getProperty(key);
				Resource resource = getResource(path);
				this.subReports.put(key, loadReport(resource));
			}
		}

		// Load sub reports if required, and check data source parameters.
		if (this.templateUrls != null) {
			this.templates = new HashMap<String, JRTemplate>(this.templateUrls.size());
			for (Enumeration urls = this.templateUrls.propertyNames(); urls.hasMoreElements();) {
				String key = (String) urls.nextElement();
				String path = this.templateUrls.getProperty(key);
				Resource resource = getResource(path);
				this.templates.put(key, loadTemplate(resource));
			}
		}

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

		// native support for pdf/a compliance without the need for a specific icc file
		// we only add this if the parameter hasn't already been specified
		if (!this.convertedExporterParameters.containsKey(JRPdfExporterParameter.PDFA_ICC_PROFILE_PATH)) {
			this.convertedExporterParameters.put(JRPdfExporterParameter.PDFA_ICC_PROFILE_PATH, DEFAULT_ICC_PROFILE);
		}
		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() {
		this.convertedExporterParameters = new HashMap<JRExporterParameter, Object>();
		if (!CollectionUtils.isEmpty(this.exporterParameters)) {
			for (Map.Entry<?, ?> entry : this.exporterParameters.entrySet()) {
				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.
					// We make last try to see if it is a static FIELD
					return convertFieldToObjectValue(str);
				}
			} else {
				return convertFieldToObjectValue(str);
			}
		}
		return value;
	}
	
	/**
	 * 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() {
	}

	/**
	 * 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 = getResource(url);
		//Resource mainReport2 = getApplicationContext().getResource(url);
		return loadReport(mainReport);
	}
	
	private Resource getResource(String url) {
		Assert.notNull(url, "Location must not be null");
		if (url.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
			return new UrlResource(ClassUtils.getDefaultClassLoader().getResource(url.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length())));
		}
		return getApplicationContext().getResource(url);
	}

	/**
	 * 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);
				}
				InputStream is = resource.getInputStream();
				try {
					return (JasperReport) JRLoader.loadObject(is);
				}
				finally {
					is.close();
				}
			}
			else if (fileName.endsWith(".jrxml")) {
				// Compile report on-the-fly.
				if (logger.isInfoEnabled()) {
					logger.info("Compiling Jasper Report loaded from " + resource);
				}
				InputStream is = resource.getInputStream();
				try {
					JasperDesign design = JRXmlLoader.load(is);
					return JasperCompileManager.compileReport(design);
				}
				finally {
					is.close();
				}
			}
			else {
				throw new IllegalArgumentException(
						"Report filename [" + fileName + "] must end in either .jasper or .jrxml");
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException(
					"Could not load JasperReports report from " + resource, ex);
		}
		catch (JRException ex) {
			throw new ApplicationContextException(
					"Could not parse JasperReports report from " + resource, ex);
		}
	}


	/**
	 * 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 JRTemplate loadTemplate(Resource resource) {
		try {
			String fileName = resource.getFilename();
			if (fileName.endsWith(".jrtx")) {
				// Load pre-compiled report template.
				if (logger.isInfoEnabled()) {
					logger.info("Loading Jasper Report Template from " + resource);
				}
				InputStream is = resource.getInputStream();
				try {
					return JRXmlTemplateLoader.load(is);
				}
				finally {
					is.close();
				}
			} else {
				throw new IllegalArgumentException(
						"Report Template filename [" + fileName + "] must end with .jrtx");
			}
		}
		catch (IOException ex) {
			throw new ApplicationContextException(
					"Could not load JasperReports report template from " + resource, ex);
		}
	}


	/**
	 * 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, getApplicationContext().getClassLoader());
			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.");
				}
			}
			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 + "].");
		}
	}

	/**
	 * Convert the given fully qualified field name to a corresponding
	 * JRExporterParameter value.
	 * @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 instance value
	 */
	protected Object convertFieldToObjectValue(String fqFieldName) {
		int index = fqFieldName.lastIndexOf('.');
		if (index == -1 || index == fqFieldName.length()) {
			return fqFieldName;
		}
		try {
			String className = fqFieldName.substring(0, index);
			String fieldName = fqFieldName.substring(index + 1);

			Class cls = ClassUtils.forName(className, getApplicationContext().getClassLoader());
			Field field = cls.getField(fieldName);

			return field.get(null);
		}
		catch (Exception ex) {
			return fqFieldName;
		}
	}

}
