package edu.ualberta.med.biobank.reporting; /* * DynamicJasper: A library for creating reports dynamically by specifying * columns, groups, styles, etc. at runtime. It also saves a lot of development * time in many cases! (http://sourceforge.net/projects/dynamicjasper) * * Copyright (C) 2008 FDV Solutions (http://www.fdvsolutions.com) * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * */ import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.net.URL; import java.sql.Connection; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; import net.sf.jasperreports.engine.JRDataSource; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRResultSetDataSource; 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.data.JRBeanCollectionDataSource; import net.sf.jasperreports.engine.design.JRCompiler; import net.sf.jasperreports.engine.design.JRDesignField; import net.sf.jasperreports.engine.design.JRDesignGroup; import net.sf.jasperreports.engine.design.JRDesignParameter; import net.sf.jasperreports.engine.design.JRDesignQuery; import net.sf.jasperreports.engine.design.JasperDesign; import net.sf.jasperreports.engine.util.JRProperties; import net.sf.jasperreports.engine.xml.JRXmlLoader; import net.sf.jasperreports.engine.xml.JRXmlWriter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import ar.com.fdvs.dj.core.CoreException; import ar.com.fdvs.dj.core.DJDefaultScriptlet; import ar.com.fdvs.dj.core.DJException; import ar.com.fdvs.dj.core.DJJRDesignHelper; import ar.com.fdvs.dj.core.layout.LayoutManager; import ar.com.fdvs.dj.core.registration.ColumnRegistrationManager; import ar.com.fdvs.dj.core.registration.DJGroupRegistrationManager; import ar.com.fdvs.dj.core.registration.DJGroupVariableDefRegistrationManager; import ar.com.fdvs.dj.domain.ColumnProperty; import ar.com.fdvs.dj.domain.DJCalculation; import ar.com.fdvs.dj.domain.DynamicJasperDesign; import ar.com.fdvs.dj.domain.DynamicReport; import ar.com.fdvs.dj.domain.DynamicReportOptions; import ar.com.fdvs.dj.domain.constants.Page; import ar.com.fdvs.dj.domain.entities.DJGroup; import ar.com.fdvs.dj.domain.entities.DJGroupVariableDef; import ar.com.fdvs.dj.domain.entities.Parameter; import ar.com.fdvs.dj.domain.entities.Subreport; import ar.com.fdvs.dj.domain.entities.columns.AbstractColumn; import ar.com.fdvs.dj.domain.entities.columns.PercentageColumn; import ar.com.fdvs.dj.util.DJCompilerFactory; import ar.com.fdvs.dj.util.LayoutUtils; /** * Helper class for running a report and some other DJ related stuff * * DD: this class and 2 or 3 others methods has been copied from the * DynamicJasper library. It is expecting a url searched into the same * classloader than dynamicjasper. But dynamicjasper is in another plugin so it * can't find the url for the jasper file that is in this plugin. Since this * class is in biobank2 plugin then the url is found. (see issue #1239) */ @SuppressWarnings({ "unchecked", "rawtypes", "unused" }) public class DynamicJasperHelper { private static final Log log = LogFactory.getLog(DynamicJasperHelper.class); public static final String DEFAULT_XML_ENCODING = "UTF-8"; //$NON-NLS-1$ private static final String DJ_RESOURCE_BUNDLE = DynamicJasperHelper.class .getPackage().getName() + ".dj-messages"; //$NON-NLS-1$ private final static void registerEntities(DynamicJasperDesign jd, DynamicReport dr, LayoutManager layoutManager) { ColumnRegistrationManager columnRegistrationManager = new ColumnRegistrationManager( jd, dr, layoutManager); columnRegistrationManager.registerEntities(dr.getColumns()); DJGroupRegistrationManager djGroupRegistrationManager = new DJGroupRegistrationManager( jd, dr, layoutManager); djGroupRegistrationManager.registerEntities(dr.getColumnsGroups()); registerPercentageColumnsVariables(jd, dr, layoutManager); registerOtherFields(jd, dr.getFields()); Locale locale = dr.getReportLocale() == null ? Locale.getDefault() : dr .getReportLocale(); if (log.isDebugEnabled()) { log.debug("Requested Locale = " + dr.getReportLocale() //$NON-NLS-1$ + ", Locale to use: " + locale); //$NON-NLS-1$ } ResourceBundle messages = null; if (dr.getResourceBundle() != null) { try { messages = ResourceBundle.getBundle(dr.getResourceBundle(), locale); } catch (MissingResourceException e) { log.warn(e.getMessage() + ", usign default (dj-messages)"); //$NON-NLS-1$ } } if (messages == null) { try { messages = ResourceBundle.getBundle(DJ_RESOURCE_BUNDLE, locale); } catch (MissingResourceException e) { log.warn(e.getMessage() + ", usign default (dj-messages)"); //$NON-NLS-1$ try { messages = ResourceBundle.getBundle(DJ_RESOURCE_BUNDLE, Locale.ENGLISH); // this cannot fail because is included // in the DJ jar } catch (MissingResourceException e2) { log.error("Default messajes not found: " //$NON-NLS-1$ + DJ_RESOURCE_BUNDLE + ", " + e2.getMessage(), e2); //$NON-NLS-1$ throw new DJException("Default messajes file not found: " //$NON-NLS-1$ + DJ_RESOURCE_BUNDLE + "en.properties", e2); //$NON-NLS-1$ } } } jd.getParametersWithValues().put( JRDesignParameter.REPORT_RESOURCE_BUNDLE, messages); jd.getParametersWithValues().put(JRDesignParameter.REPORT_LOCALE, locale); // JRDesignParameter.REPORT_RESOURCE_BUNDLE // report. } private static void registerPercentageColumnsVariables( DynamicJasperDesign jd, DynamicReport dr, LayoutManager layoutManager) { for (Iterator iterator = dr.getColumns().iterator(); iterator.hasNext();) { AbstractColumn column = (AbstractColumn) iterator.next(); // if (column instanceof PercentageColumn) { // PercentageColumn percentageColumn = ((PercentageColumn) column); // JRDesignGroup jrGroup = LayoutUtils.getJRDesignGroup(jd, // layoutManager, percentageColumn.getGroup()); // ColumnsGroupTemporalVariablesRegistrationManager variablesRM = // new // ColumnsGroupTemporalVariablesRegistrationManager(jd,dr,layoutManager, // jrGroup); // DJGroupTemporalVariable variable = new // DJGroupTemporalVariable(percentageColumn.getGroupVariableName(), // percentageColumn.getPercentageColumn(), DJCalculation.SUM); // Collection entities = new ArrayList(); // entities.add(variable); // variablesRM.registerEntities(entities); // } /** * Group should not be needed in the percentage column. There should * be a variable for each group, using parent group as "rest group" */ if (column instanceof PercentageColumn) { PercentageColumn percentageColumn = ((PercentageColumn) column); for (Iterator iterator2 = dr.getColumnsGroups().iterator(); iterator2 .hasNext();) { DJGroup djGroup = (DJGroup) iterator2.next(); JRDesignGroup jrGroup = LayoutUtils.getJRDesignGroup(jd, layoutManager, djGroup); DJGroupVariableDefRegistrationManager variablesRM = new DJGroupVariableDefRegistrationManager( jd, dr, layoutManager, jrGroup); DJGroupVariableDef variable = new DJGroupVariableDef( percentageColumn.getGroupVariableName(djGroup), percentageColumn.getPercentageColumn(), DJCalculation.SUM); Collection entities = new ArrayList(); entities.add(variable); variablesRM.registerEntities(entities); } } } } private static void registerOtherFields(DynamicJasperDesign jd, List fields) { for (Iterator iter = fields.iterator(); iter.hasNext();) { ColumnProperty element = (ColumnProperty) iter.next(); JRDesignField field = new JRDesignField(); field.setValueClassName(element.getValueClassName()); field.setName(element.getProperty()); try { jd.addField(field); } catch (JRException e) { // e.printStackTrace(); // if the field is already registered, it's not a problem log.warn(e.getMessage()); } } } protected static DynamicJasperDesign generateJasperDesign(DynamicReport dr) throws CoreException { DynamicJasperDesign jd = null; try { if (dr.getTemplateFileName() != null) { log.info("about to load template file: " //$NON-NLS-1$ + dr.getTemplateFileName() + ", Attemping to find the file directly in the file system."); //$NON-NLS-1$ File file = new File(dr.getTemplateFileName()); if (file.exists()) { JasperDesign jdesign = JRXmlLoader.load(file); jd = DJJRDesignHelper.downCast(jdesign, dr); } else { log.info("Not found: Attemping to find the file in the classpath..."); //$NON-NLS-1$ URL url = DynamicJasperHelper.class.getClassLoader() .getResource(dr.getTemplateFileName()); JasperDesign jdesign = JRXmlLoader.load(url.openStream()); jd = DJJRDesignHelper.downCast(jdesign, dr); } populateReportOptionsFromDesign(jd, dr); } else { // Create new JasperDesign from the scratch jd = DJJRDesignHelper.getNewDesign(dr); } jd.setScriptletClass(DJDefaultScriptlet.class.getName()); // Set up // scripttlet // so that // custom // expressions // can do // their // magic registerParameters(jd, dr); } catch (JRException e) { throw new CoreException(e.getMessage(), e); } catch (IOException e) { throw new CoreException(e.getMessage(), e); } return jd; } /** * Because all the layout calculations are made from the Domain Model of * DynamicJasper, when loading a template file, we have to populate the * "ReportOptions" with the settings from the template file (ie: margins, * etc) * * @param jd * @param dr */ protected static void populateReportOptionsFromDesign( DynamicJasperDesign jd, DynamicReport dr) { DynamicReportOptions options = dr.getOptions(); options.setBottomMargin(new Integer(jd.getBottomMargin())); options.setTopMargin(new Integer(jd.getTopMargin())); options.setLeftMargin(new Integer(jd.getLeftMargin())); options.setRightMargin(new Integer(jd.getRightMargin())); options.setColumnSpace(new Integer(jd.getColumnSpacing())); options.setColumnsPerPage(new Integer(jd.getColumnCount())); options.setPage(new Page(jd.getPageHeight(), jd.getPageWidth())); if (dr.getQuery() != null) { JRDesignQuery query = getJRDesignQuery(dr); jd.setQuery(query); } if (dr.getReportName() != null) { jd.setName(dr.getReportName()); } } protected static JRDesignQuery getJRDesignQuery(DynamicReport dr) { JRDesignQuery query = new JRDesignQuery(); query.setText(dr.getQuery().getText()); query.setLanguage(dr.getQuery().getLanguage()); return query; } protected static void registerParameters(DynamicJasperDesign jd, DynamicReport dr) { for (Iterator iterator = dr.getParameters().iterator(); iterator .hasNext();) { Parameter param = (Parameter) iterator.next(); JRDesignParameter jrparam = new JRDesignParameter(); jrparam.setName(param.getName()); jrparam.setValueClassName(param.getClassName()); try { jd.addParameter(jrparam); } catch (JRException e) { throw new CoreException(e.getMessage(), e); } } } public static JasperPrint generateJasperPrint(DynamicReport dr, LayoutManager layoutManager, JRDataSource ds) throws JRException { return generateJasperPrint(dr, layoutManager, ds, new HashMap()); } public static JasperPrint generateJasperPrint(DynamicReport dr, LayoutManager layoutManager, Collection collection) throws JRException { JRDataSource ds = new JRBeanCollectionDataSource(collection); return generateJasperPrint(dr, layoutManager, ds, new HashMap()); } public static JasperPrint generateJasperPrint(DynamicReport dr, LayoutManager layoutManager, ResultSet resultSet) throws JRException { JRDataSource ds = new JRResultSetDataSource(resultSet); return generateJasperPrint(dr, layoutManager, ds, new HashMap()); } /** * Compiles and fills the reports design. * * @param dr the DynamicReport * @param layoutManager the object in charge of doing the layout * @param ds The datasource * @param _parameters Map with parameters that the report may need * @return * @throws JRException */ public static JasperPrint generateJasperPrint(DynamicReport dr, LayoutManager layoutManager, JRDataSource ds, Map _parameters) throws JRException { log.info("generating JasperPrint"); //$NON-NLS-1$ JasperPrint jp = null; // if (_parameters == null) // _parameters = new HashMap(); // // visitSubreports(dr, _parameters); // compileOrLoadSubreports(dr, _parameters); // // DynamicJasperDesign jd = generateJasperDesign(dr); // Map params = new HashMap(); // if (!_parameters.isEmpty()){ // registerParams(jd,_parameters); // params.putAll(_parameters); // } // // registerEntities(jd, dr); // layoutManager.applyLayout(jd, dr); // JRProperties.setProperty(JRProperties.COMPILER_CLASS, // DJCompilerFactory.getCompilerClassName()); // // JasperReport jr = JasperCompileManager.compileReport(jd); // params.putAll(jd.getParametersWithValues()); JasperReport jr = DynamicJasperHelper.generateJasperReport(dr, layoutManager, _parameters); jp = JasperFillManager.fillReport(jr, _parameters, ds); return jp; } /** * For running queries embebed in the report design * * @param dr * @param layoutManager * @param con * @param _parameters * @return * @throws JRException */ public static JasperPrint generateJasperPrint(DynamicReport dr, LayoutManager layoutManager, Connection con, Map _parameters) throws JRException { log.info("generating JasperPrint"); //$NON-NLS-1$ JasperPrint jp = null; if (_parameters == null) _parameters = new HashMap(); visitSubreports(dr, _parameters); compileOrLoadSubreports(dr, _parameters); DynamicJasperDesign jd = generateJasperDesign(dr); Map params = new HashMap(); if (!_parameters.isEmpty()) { registerParams(jd, _parameters); params.putAll(_parameters); } registerEntities(jd, dr, layoutManager); layoutManager.applyLayout(jd, dr); JRProperties.setProperty(JRCompiler.COMPILER_PREFIX, DJCompilerFactory.getCompilerClassName()); JasperReport jr = JasperCompileManager.compileReport(jd); params.putAll(jd.getParametersWithValues()); jp = JasperFillManager.fillReport(jr, params, con); return jp; } /** * For compiling and filling reports whose datasource is passed as parameter * (e.g. Hibernate, Mondrean, etc.) * * @param dr * @param layoutManager * @param _parameters * @return * @throws JRException */ public static JasperPrint generateJasperPrint(DynamicReport dr, LayoutManager layoutManager, Map _parameters) throws JRException { log.info("generating JasperPrint"); //$NON-NLS-1$ JasperPrint jp = null; if (_parameters == null) _parameters = new HashMap(); visitSubreports(dr, _parameters); compileOrLoadSubreports(dr, _parameters); DynamicJasperDesign jd = generateJasperDesign(dr); Map params = new HashMap(); if (!_parameters.isEmpty()) { registerParams(jd, _parameters); params.putAll(_parameters); } registerEntities(jd, dr, layoutManager); layoutManager.applyLayout(jd, dr); // JRProperties.setProperty(JRProperties.COMPILER_CLASS, // DJCompilerFactory.getCompilerClassName()); JRProperties.setProperty(JRCompiler.COMPILER_PREFIX, DJCompilerFactory.getCompilerClassName()); JasperReport jr = JasperCompileManager.compileReport(jd); params.putAll(jd.getParametersWithValues()); jp = JasperFillManager.fillReport(jr, params); return jp; } /** * Creates a jrxml file * * @param dr * @param layoutManager * @param _parameters * @param xmlEncoding (default is UTF-8 ) * @return * @throws JRException */ public static String generateJRXML(DynamicReport dr, LayoutManager layoutManager, Map _parameters, String xmlEncoding) throws JRException { JasperReport jr = generateJasperReport(dr, layoutManager, _parameters); if (xmlEncoding == null) xmlEncoding = DEFAULT_XML_ENCODING; return JRXmlWriter.writeReport(jr, xmlEncoding); } /** * Creates a jrxml file * * @param dr * @param layoutManager * @param _parameters * @param xmlEncoding (default is UTF-8 ) * @param outputStream * @throws JRException */ public static void generateJRXML(DynamicReport dr, LayoutManager layoutManager, Map _parameters, String xmlEncoding, OutputStream outputStream) throws JRException { JasperReport jr = generateJasperReport(dr, layoutManager, _parameters); if (xmlEncoding == null) xmlEncoding = DEFAULT_XML_ENCODING; JRXmlWriter.writeReport(jr, outputStream, xmlEncoding); } /** * Creates a jrxml file * * @param dr * @param layoutManager * @param _parameters * @param xmlEncoding (default is UTF-8 ) * @param filename the path to the destination file * @throws JRException */ public static void generateJRXML(DynamicReport dr, LayoutManager layoutManager, Map _parameters, String xmlEncoding, String filename) throws JRException { JasperReport jr = generateJasperReport(dr, layoutManager, _parameters); if (xmlEncoding == null) xmlEncoding = DEFAULT_XML_ENCODING; ensurePath(filename); JRXmlWriter.writeReport(jr, filename, xmlEncoding); } public static void generateJRXML(JasperReport jr, String xmlEncoding, String filename) throws JRException { if (xmlEncoding == null) xmlEncoding = DEFAULT_XML_ENCODING; ensurePath(filename); JRXmlWriter.writeReport(jr, filename, xmlEncoding); } private static void ensurePath(String filename) { File outputFile = new File(filename); File parentFile = outputFile.getParentFile(); if (parentFile != null) parentFile.mkdirs(); } protected static void compileOrLoadSubreports(DynamicReport dr, Map _parameters) throws JRException { for (Iterator iterator = dr.getColumnsGroups().iterator(); iterator .hasNext();) { DJGroup group = (DJGroup) iterator.next(); // Header Subreports for (Iterator iterator2 = group.getHeaderSubreports().iterator(); iterator2 .hasNext();) { Subreport subreport = (Subreport) iterator2.next(); if (subreport.getDynamicReport() != null) { compileOrLoadSubreports(subreport.getDynamicReport(), _parameters); JasperReport jp = generateJasperReport( subreport.getDynamicReport(), subreport.getLayoutManager(), _parameters); _parameters.put(jp.toString(), jp); subreport.setReport(jp); } } // Footer Subreports for (Iterator iterator2 = group.getFooterSubreports().iterator(); iterator2 .hasNext();) { Subreport subreport = (Subreport) iterator2.next(); if (subreport.getDynamicReport() != null) { compileOrLoadSubreports(subreport.getDynamicReport(), _parameters); JasperReport jp = generateJasperReport( subreport.getDynamicReport(), subreport.getLayoutManager(), _parameters); _parameters.put(jp.toString(), jp); subreport.setReport(jp); } } } } /** * For every String key, it registers the object as a parameter to make it * available in the report. * * @param jd * @param _parameters */ public static void registerParams(DynamicJasperDesign jd, Map _parameters) { for (Iterator iterator = _parameters.keySet().iterator(); iterator .hasNext();) { Object key = iterator.next(); if (key instanceof String) { try { Object value = _parameters.get(key); if (jd.getParametersMap().get(key) != null) { log.warn("Parameter \"" + key //$NON-NLS-1$ + "\" already registered, skipping this one: " //$NON-NLS-1$ + value); continue; } JRDesignParameter parameter = new JRDesignParameter(); if (value == null) // There are some Map implementations // that allows nulls values, just go on continue; // parameter.setValueClassName(value.getClass().getCanonicalName()); Class clazz = value.getClass().getComponentType(); if (clazz == null) clazz = value.getClass(); parameter.setValueClass(clazz); // NOTE this is very strange // when using an array as subreport-data-source, I must pass // the parameter class name like this: // value.getClass().getComponentType() parameter.setName((String) key); jd.addParameter(parameter); } catch (JRException e) { // nothing to do } } } } /** * Compiles the report and applies the layout. <b>generatedParams</b> MUST * NOT BE NULL All the key objects from the generatedParams map that are * String, will be registered as parameters of the report. * * @param dr * @param layoutManager * @param generatedParams * @return * @throws JRException */ public final static JasperReport generateJasperReport(DynamicReport dr, LayoutManager layoutManager, Map generatedParams) throws JRException { log.info("generating JasperReport"); //$NON-NLS-1$ JasperReport jr = null; if (generatedParams == null) generatedParams = new HashMap(); visitSubreports(dr, generatedParams); compileOrLoadSubreports(dr, generatedParams); DynamicJasperDesign jd = generateJasperDesign(dr); registerEntities(jd, dr, layoutManager); registerParams(jd, generatedParams); // if we have parameters from the // outside, we register them layoutManager.applyLayout(jd, dr); JRProperties.setProperty(JRCompiler.COMPILER_PREFIX, "ar.com.fdvs.dj.util.DJJRJdtCompiler"); //$NON-NLS-1$ jr = JasperCompileManager.compileReport(jd); generatedParams.putAll(jd.getParametersWithValues()); return jr; } /** * Performs any needed operation on subreports after they are built like * ensuring proper subreport with if "fitToParentPrintableArea" flag is set * to true * * @param dr * @param _parameters * @throws JRException */ protected static void visitSubreports(DynamicReport dr, Map _parameters) throws JRException { for (Iterator iterator = dr.getColumnsGroups().iterator(); iterator .hasNext();) { DJGroup group = (DJGroup) iterator.next(); // Header Subreports for (Iterator iterator2 = group.getHeaderSubreports().iterator(); iterator2 .hasNext();) { Subreport subreport = (Subreport) iterator2.next(); if (subreport.getDynamicReport() != null) { visitSubreport(dr, subreport, _parameters); visitSubreports(subreport.getDynamicReport(), _parameters); } } // Footer Subreports for (Iterator iterator2 = group.getFooterSubreports().iterator(); iterator2 .hasNext();) { Subreport subreport = (Subreport) iterator2.next(); if (subreport.getDynamicReport() != null) { visitSubreport(dr, subreport, _parameters); visitSubreports(subreport.getDynamicReport(), _parameters); } } } } protected static void visitSubreport(DynamicReport parentDr, Subreport subreport, Map _parameters) { DynamicReport childDr = subreport.getDynamicReport(); if (subreport.isFitToParentPrintableArea()) { childDr.getOptions().setPage(parentDr.getOptions().getPage()); childDr.getOptions().setLeftMargin( parentDr.getOptions().getLeftMargin()); childDr.getOptions().setRightMargin( parentDr.getOptions().getRightMargin()); } } }