/******************************************************************************* * Copyright (c) 2014 Open Door Logistics (www.opendoorlogistics.com) * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser Public License v3 * which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl.txt ******************************************************************************/ package com.opendoorlogistics.components.reports.builder; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.addColumnHeaderSection; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.addDetailBand; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.addFields; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.addPageFooter; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.addTitle; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.createEmpty; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.createEmptyA4; import static com.opendoorlogistics.components.reports.builder.ReportBuilderUtils.getAvailableWidth; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; import net.sf.jasperreports.engine.JRBand; import net.sf.jasperreports.engine.JRDataSource; import net.sf.jasperreports.engine.JRElement; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JRField; import net.sf.jasperreports.engine.JasperCompileManager; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.JasperPrint; import net.sf.jasperreports.engine.design.JRDesignBand; import net.sf.jasperreports.engine.design.JRDesignExpression; import net.sf.jasperreports.engine.design.JRDesignParameter; import net.sf.jasperreports.engine.design.JRDesignSection; import net.sf.jasperreports.engine.design.JRDesignSubreport; import net.sf.jasperreports.engine.design.JRDesignSubreportParameter; import net.sf.jasperreports.engine.design.JasperDesign; import net.sf.jasperreports.engine.type.OrientationEnum; import net.sf.jasperreports.engine.xml.JRXmlWriter; import com.opendoorlogistics.api.ODLApi; import com.opendoorlogistics.api.components.ContinueProcessingCB; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLTableDefinition; import com.opendoorlogistics.api.tables.ODLTableReadOnly; import com.opendoorlogistics.api.tables.TableFlags; import com.opendoorlogistics.components.reports.ReportConstants; import com.opendoorlogistics.core.reports.ImageProvider; import com.opendoorlogistics.core.tables.memory.ODLTableDefinitionImpl; import com.opendoorlogistics.core.tables.utils.DatastoreCopier; import com.opendoorlogistics.core.tables.utils.TableUtils; import com.opendoorlogistics.core.utils.Serialization; import com.opendoorlogistics.core.utils.strings.Strings; final public class SubreportsWithProviderBuilder { private static void setupDatasourceParameters(JasperDesign report, boolean hasHeaderMap) { try { // ensure every subreport has the provider parameter for (JRBand band : report.getDetailSection().getBands()) { for (JRElement element : band.getElements()) { if (JRDesignSubreport.class.isInstance(element)) { JRDesignSubreport sub = (JRDesignSubreport) element; sub.removeParameter(ReportConstants.DATASOURCE_PROVIDER_PARAMETER); JRDesignSubreportParameter param = new JRDesignSubreportParameter(); param.setName(ReportConstants.DATASOURCE_PROVIDER_PARAMETER); param.setExpression(new JRDesignExpression("$P{" + ReportConstants.DATASOURCE_PROVIDER_PARAMETER + "}")); sub.addParameter(param); } } } // ensure main report has correct provider parameters addParameter(report, ReportConstants.DATASOURCE_PROVIDER_PARAMETER); if (hasHeaderMap) { addParameter(report, ReportConstants.HEADER_MAP_PROVIDER_PARAMETER); } } catch (Throwable e) { throw new RuntimeException(e); } } private static void addParameter(JasperDesign report, String paramName) { try { report.removeParameter(paramName); JRDesignParameter param = new JRDesignParameter(); param.setName(paramName); param.setValueClass(Object.class); report.addParameter(param); } catch (Throwable e) { throw new RuntimeException(e); } } // // public static boolean buildTwoLevelToFile(ODLDatastoreAlterable<? extends ODLTableReadOnly> ds, String masterTableName, String subTableName, // String masterKeyfield, String subkeyfield, OrientationEnum orientation, File dir) { // return buildTwoLevelToFile(ds, masterTableName, subTableName, masterKeyfield, subkeyfield, orientation, dir, // Strings.toFileSafe(masterTableName), // Strings.toFileSafe(subTableName)); // } public static class BuildResult { public final List<String> tableNames = new ArrayList<>(); public final List<String> keyfields = new ArrayList<>(); public final List<String> baseFilenames = new ArrayList<>(); public final List<JasperDesign> designs = new ArrayList<>(); } /** * Build templates where the first table is the master and the other tables are subreports each on the same level. * * @param ds * @return */ public static BuildResult buildWizard(ODLApi api,ODLDatastore<? extends ODLTableDefinition> ds, String title, OrientationEnum orientation) { // get tables, removing header map table boolean hasHeaderMap = false; ArrayList<ODLTableDefinition> tables = new ArrayList<>(); for (int i = 0; i < ds.getTableCount(); i++) { ODLTableDefinition dfn = ds.getTableAt(i); if (Strings.equalsStd(api.standardComponents().reporter().getHeaderMapTableName(), dfn.getName())) { hasHeaderMap = true; } else { tables.add(dfn); } } if (tables.size() == 0 && hasHeaderMap == false) { throw new RuntimeException("No input details table or header map table given. Report will be empty."); } // validate flags and get keyfield names BuildResult ret = new BuildResult(); for (ODLTableDefinition table : tables) { if (table.getColumnCount() == 0) { throw new RuntimeException("Found empty table: " + table.getName()); } if (tables.size() > 1) { if (TableUtils.countNbColumnsWithFlag(table, TableFlags.FLAG_IS_REPORT_KEYFIELD) != 1) { throw new RuntimeException("Table either has 0 or more than 1 columns with the report keyfield set: " + table.getName()); } int indx = TableUtils.findColumnIndexWithFlag(table, TableFlags.FLAG_IS_REPORT_KEYFIELD); ret.keyfields.add(table.getColumnName(indx)); if (table.getColumnCount() == 1) { throw new RuntimeException("Found empty table: " + table.getName()); } } else { ret.keyfields.add(null); } ret.tableNames.add(table.getName()); } // work out filenames if (tables.size() == 0) { ret.baseFilenames.add("Report"); ret.tableNames.add("Header map"); ret.keyfields.add(null); } else { for (ODLTableDefinition table : tables) { String tablename = table.getName(); String name = Strings.toFileSafeString(tablename); if (name.length() == 0 || Strings.containsStandardised(name, ret.baseFilenames)) { throw new RuntimeException("Table name will be empty or repeated when converted to a file: " + tablename); } ret.baseFilenames.add(name); } } // get designs if (tables.size() <= 1) { // single level report JasperDesign design = new SingleLevelReportBuilder().buildSingleTableDesign(tables.size() > 0 ? tables.get(0) : null, title, orientation, hasHeaderMap); if (hasHeaderMap) { addParameter(design, ReportConstants.HEADER_MAP_PROVIDER_PARAMETER); } ret.designs.add(design); } else { // master and subreport(s) // build master ODLTableDefinition masterTable = tables.get(0); JasperDesign master = createEmptyA4(masterTable.getName(), orientation, true, 0); addFields(masterTable, false, master); addTitle(title != null ? title : masterTable.getName(), hasHeaderMap, true, master); // addColumnHeaderSection(masterTable, getAvailableWidth(master),master); int headerRowHeight = addDetailBand(masterTable, getAvailableWidth(master), true, master); addPageFooter(master); setupDatasourceParameters(master, hasHeaderMap); ret.designs.add(master); // work out width for each subreport int nbSubs = tables.size() - 1; int width = getAvailableWidth(master); int gap = 30; // we indent by gap and also have gap between each report width -= nbSubs * gap; int subWidth = width / nbSubs; // build multiple subreports, adding to the master band JRDesignBand masterBand = (JRDesignBand) master.getDetailSection().getBands()[0]; int x = gap; int maxSubreportRowHeight = 0; for (int i = 1; i < tables.size(); i++) { // get table but with keyfield removed (no point showing it) ODLTableDefinitionImpl subTable = new ODLTableDefinitionImpl(); subTable.setName(tables.get(i).getName()); DatastoreCopier.copyTableDefinition(tables.get(i), subTable); subTable.deleteColumn(TableUtils.findColumnIndexWithFlag(subTable, TableFlags.FLAG_IS_REPORT_KEYFIELD)); // build subreport template JasperDesign subTemplate = createEmpty(subTable.getName(), subWidth, master.getPageHeight(), false); addColumnHeaderSection(subTable, getAvailableWidth(subTemplate), subTemplate); addFields(subTable, false, subTemplate); int subreportRowHeight = addDetailBand(subTable, getAvailableWidth(subTemplate), false, subTemplate); maxSubreportRowHeight = Math.max(subreportRowHeight, maxSubreportRowHeight); setupDatasourceParameters(subTemplate, false); // build subreport reference in master template String dataExpression = createSubreportDataExpression(subTable.getName(), ret.keyfields.get(0), ret.keyfields.get(i)); String subreportExpression = "\"" + ret.baseFilenames.get(i) + ".jasper\""; JRDesignSubreport subRef = ReportBuilderUtils.createSubreport(x, headerRowHeight + 2, subreportRowHeight, subreportExpression, dataExpression, master); masterBand.addElement(subRef); ret.designs.add(subTemplate); x += gap + subWidth; } // set the master band height at the end masterBand.setHeight(headerRowHeight + maxSubreportRowHeight + 4); } return ret; } private static String createSubreportDataExpression(String subTableName, String masterKeyfield, String subkeyfield) { return "((" + ReportConstants.DATASOURCE_PROVIDER_INTERFACE + ")" + "$P{" + ReportConstants.DATASOURCE_PROVIDER_PARAMETER + "})." + ReportConstants.DATASOURCE_PROVIDER_INTERFACE_METHOD + "(\"" + subTableName + "\",\"" + subkeyfield + "\",$F{" + masterKeyfield + "})"; } public static JasperPrint fillReport(ODLDatastore<? extends ODLTableReadOnly> ds, int tableIndx, ODLTableReadOnly mapHeaderTable, String compiledReportFilename, ContinueProcessingCB continueCB) { try { JRDataSource rds = null; if (ds.getTableCount() > 0) { rds = new SingleLevelReportDatasource(ds.getTableAt(tableIndx), continueCB); } else { // dummy datasource rds = new JRDataSource() { @Override public boolean next() throws JRException { return false; } @Override public Object getFieldValue(JRField arg0) throws JRException { return null; } }; } TreeMap<String, Object> params = new TreeMap<>(); params.put(ReportConstants.DATASOURCE_PROVIDER_PARAMETER, new SubreportDatasourceProviderImpl(ds, continueCB)); if (mapHeaderTable != null) { ImageProvider provider = new ImageProvider(mapHeaderTable); params.put(ReportConstants.HEADER_MAP_PROVIDER_PARAMETER, provider); } JasperPrint print = JasperFillManager.fillReport(compiledReportFilename, params, rds); return print; } catch (Throwable e) { throw new RuntimeException(e); } } // public static void main(String[] args) throws Exception { // ODLDatastoreAlterable<ODLTableAlterable> ds = ExampleData.createTerritoriesExample(1); // System.out.println(ds); // // BuildResult result = buildWizard(ds, "Test", OrientationEnum.LANDSCAPE); // // String directory = "c:\\temp\\reportTest3"; // String prefix = ""; // // List<String> absFilenames = exportResultFiles(result, directory, prefix, true, true); // // JasperPrint print = fillReport(ds, 0, null, absFilenames.get(0), null); // // ShowPanel.showPanel(new JRViewer(print)); // // } /** * Export the result files and return a list of the compile filenames * * @param result * @param directory * @param prefix * @return * @throws JRException */ public static List<String> exportResultFiles(BuildResult result, String directory, String prefix, boolean exportJrxml, boolean exportCompiled) throws JRException { File dir = new File(directory); if (dir.exists() == false && !dir.mkdirs()) { return null; } ArrayList<String> absFilenames = new ArrayList<>(); for (int i = 0; i < result.designs.size(); i++) { JasperDesign design = result.designs.get(i); // update subreport reference to use the prefix. Do this on a deep copy of the design design = (JasperDesign) Serialization.deepCopy(design); JRDesignSection details = (JRDesignSection) design.getDetailSection(); if (details != null) { for (JRBand band : details.getBandsList()) { JRDesignBand designBand = (JRDesignBand) band; for (JRElement element : designBand.getElements()) { if (JRDesignSubreport.class.isInstance(element)) { JRDesignSubreport sub = (JRDesignSubreport) element; JRDesignExpression expression = (JRDesignExpression) sub.getExpression(); String newExpression = "\"" + prefix + expression.getText().replaceAll("\"", "") + "\""; expression.setText(newExpression); } } } } if (exportJrxml) { JRXmlWriter.writeReport(design, dir.getAbsolutePath() + File.separator + prefix + result.baseFilenames.get(i) + ".jrxml", "UTF-8"); } if (exportCompiled) { String absFilename = dir.getAbsolutePath() + File.separator + prefix + result.baseFilenames.get(i) + ".jasper"; JasperCompileManager.compileReportToFile(design, absFilename); absFilenames.add(absFilename); } } return absFilenames; } }