/*******************************************************************************
* 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;
import java.awt.Desktop;
import java.io.File;
import java.io.Serializable;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import javax.swing.Icon;
import javax.swing.JPanel;
import net.sf.jasperreports.engine.JRAbstractExporter;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperPrint;
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.JRXlsExporter;
import net.sf.jasperreports.engine.export.oasis.JROdtExporter;
import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter;
import net.sf.jasperreports.engine.type.OrientationEnum;
import net.sf.jasperreports.swing.JRViewer;
import com.opendoorlogistics.api.ODLApi;
import com.opendoorlogistics.api.components.ComponentConfigurationEditorAPI;
import com.opendoorlogistics.api.components.ComponentControlLauncherApi;
import com.opendoorlogistics.api.components.ComponentControlLauncherApi.ControlLauncherCallback;
import com.opendoorlogistics.api.components.ComponentExecutionApi;
import com.opendoorlogistics.api.components.ODLComponent;
import com.opendoorlogistics.api.scripts.ScriptAdapter;
import com.opendoorlogistics.api.scripts.ScriptInputTables;
import com.opendoorlogistics.api.scripts.ScriptInstruction;
import com.opendoorlogistics.api.scripts.ScriptOption;
import com.opendoorlogistics.api.scripts.ScriptTemplatesBuilder;
import com.opendoorlogistics.api.scripts.ScriptTemplatesBuilder.BuildScriptCallback;
import com.opendoorlogistics.api.standardcomponents.Reporter;
import com.opendoorlogistics.api.tables.ODLDatastore;
import com.opendoorlogistics.api.tables.ODLDatastoreAlterable;
import com.opendoorlogistics.api.tables.ODLTable;
import com.opendoorlogistics.api.tables.ODLTableAlterable;
import com.opendoorlogistics.api.tables.ODLTableDefinition;
import com.opendoorlogistics.api.tables.ODLTableDefinitionAlterable;
import com.opendoorlogistics.api.tables.ODLTableReadOnly;
import com.opendoorlogistics.api.tables.TableFlags;
import com.opendoorlogistics.api.ui.Disposable;
import com.opendoorlogistics.components.reports.builder.SubreportsWithProviderBuilder;
import com.opendoorlogistics.components.reports.builder.SubreportsWithProviderBuilder.BuildResult;
import com.opendoorlogistics.core.tables.memory.ODLDatastoreImpl;
import com.opendoorlogistics.core.utils.strings.Strings;
import com.opendoorlogistics.utils.ui.Icons;
final public class ReporterComponent implements Reporter {
public static final int MODE_GENERATE_REPORTS=ODLComponent.MODE_DEFAULT;
public static final int VIEW_BASIC_LANDSCAPE=1;
public static final int VIEW_BASIC_PORTRAIT=2;
@Override
public String getId() {
return "com.opendoorlogistics.components.reports.reporter";
}
@Override
public String getName() {
return "Reporter";
}
@Override
public ODLDatastore<? extends ODLTableDefinition> getIODsDefinition(ODLApi api,Serializable configuration) {
ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> ret = api.tables().createDefinitionDs();
// add a report details table as a placeholder for the UI and wizards but make it optional as user can
// call the report input table what they want
ODLTableDefinitionAlterable reportDetails = ret.createTable("Report details", -1);
reportDetails.setFlags(reportDetails.getFlags() | TableFlags.FLAG_COLUMN_WILDCARD | TableFlags.FLAG_IS_OPTIONAL);
// add optional header
ODLTableDefinitionAlterable headerMap = api.tables().copyTableDefinition(api.standardComponents().map().getDrawableTableDefinition(), ret);
ret.setTableName(headerMap.getImmutableId(), api.standardComponents().reporter().getHeaderMapTableName());
headerMap.setFlags(headerMap.getFlags() | TableFlags.FLAG_IS_OPTIONAL);
// then set the datastore to take wildcard tables...
ret.setFlags(ret.getFlags() | TableFlags.FLAG_TABLE_WILDCARD | TableFlags.FLAG_IS_REPORTER_INPUT);
return ret;
// empty datastore means it takes all input tables
//return api.tables().createDefinitionDs();
}
@Override
public ODLDatastore<? extends ODLTableDefinition> getOutputDsDefinition(ODLApi api,int mode, Serializable configuration) {
// no output
return null;
}
@Override
public void execute(final ComponentExecutionApi reporter,int mode,Object configuration, ODLDatastore<? extends ODLTable> ioDb, ODLDatastoreAlterable<? extends ODLTableAlterable> outputDb) {
reporter.postStatusMessage("Building report");
ReporterConfig rc = (ReporterConfig) configuration;
// if compiled jasper report is not set then view a basic report
if(mode == MODE_GENERATE_REPORTS && reporter.getApi().stringConventions().isEmptyString(rc.getCompiledReport())){
mode = VIEW_BASIC_PORTRAIT;
}
// create a filtered iodb omitting the header map table
ODLDatastoreImpl<ODLTable> filtered =new ODLDatastoreImpl<>(null);
ODLTableReadOnly headerMapTable=null;
for(int i =0 ; i< ioDb.getTableCount() ; i++){
ODLTable table = ioDb.getTableAt(i);
if(Strings.equalsStd(reporter.getApi().standardComponents().reporter().getHeaderMapTableName(), table.getName())==false){
filtered.addTable(table);
}else if(table.getRowCount()>0){
// only use header map reference if non-empty...
headerMapTable = table;
}
}
try {
switch(mode){
case VIEW_BASIC_LANDSCAPE:
case VIEW_BASIC_PORTRAIT:{
// Use wizard to create a default report, save to temporary directory and then fill.
// Build wizard takes the unfiltered as it filters and handles the header map table itself
OrientationEnum orientation = mode==VIEW_BASIC_LANDSCAPE? OrientationEnum.LANDSCAPE:OrientationEnum.PORTRAIT;
BuildResult result = SubreportsWithProviderBuilder.buildWizard(reporter.getApi(),ioDb,null, orientation);
Path tempDir = null;
List<String> compiledFilenames = null;
try {
tempDir = Files.createTempDirectory("ODLReportsTemp");
compiledFilenames = SubreportsWithProviderBuilder.exportResultFiles(result, tempDir.toAbsolutePath().toString(), "", false, true);
JasperPrint print = SubreportsWithProviderBuilder.fillReport(filtered, 0, headerMapTable,compiledFilenames.get(0), reporter);
if(!reporter.isCancelled()){
exportPrintObject(rc, print, reporter);
}
} catch (Throwable e) {
throw new RuntimeException(e);
} finally {
if (tempDir != null) {
try {
// delete temporary files and directory
if (compiledFilenames != null) {
for (String compiledFilename : compiledFilenames) {
new File(compiledFilename).delete();
}
}
Files.deleteIfExists(tempDir);
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
}
}
break;
case MODE_GENERATE_REPORTS:
String compiledReport = rc.getCompiledReport();
// check file is ok
if(compiledReport==null || new File(compiledReport).exists()==false){
throw new RuntimeException("Compiled jasper reports file set in configuration doesn't exist: " + compiledReport);
}
JasperPrint print = SubreportsWithProviderBuilder.fillReport(filtered, 0,headerMapTable, compiledReport, reporter);
if(!reporter.isCancelled()){
exportPrintObject(rc, print, reporter);
}
break;
}
// // create print table
// if (Strings.isEmpty(compiledReport)) {
//
// } else {
// }
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
private void exportPrintObject(ReporterConfig rc, final JasperPrint print, final ComponentExecutionApi reporter) {
// build the export filename
String filename = buildFilename(reporter, rc);
// do each export option
if (rc.isCsv()) {
export(new JRCsvExporter(), print, filename, "csv", rc.isOpenExportFile());
}
if (rc.isDocx()) {
export(new JRDocxExporter(), print, filename, "docx", rc.isOpenExportFile());
}
if (rc.isOdt()) {
export(new JROdtExporter(), print, filename, "odt", rc.isOpenExportFile());
}
if (rc.isHtml()) {
export(new JRHtmlExporter(), print, filename, "html", rc.isOpenExportFile());
}
if (rc.isPdf()) {
export(new JRPdfExporter(), print, filename, "pdf", rc.isOpenExportFile());
}
if (rc.isXls()) {
export(new JRXlsExporter(), print, filename, "xls", rc.isOpenExportFile());
}
// do show viewer at the end so it pops up after everything else
if (rc.isShowViewer()) {
reporter.submitControlLauncher(new ControlLauncherCallback() {
@Override
public void launchControls(ComponentControlLauncherApi launcherApi) {
class DisposableViewer extends JRViewer implements Disposable {
DisposableViewer(JasperPrint jrPrint) {
super(jrPrint);
}
@Override
public void dispose() {
}
}
DisposableViewer viewer = new DisposableViewer(print);
launcherApi.registerPanel("report", null, viewer, true);
}
});
}
}
private static String buildFilename(final ComponentExecutionApi reporter, ReporterConfig rc) {
String filename = "";
if (rc.getExportDirectory() != null) {
filename += rc.getExportDirectory();
}
if (filename.length() > 0 && filename.charAt(filename.length() - 1) != File.separatorChar) {
filename += File.separatorChar;
}
if (rc.getExportFilenamePrefix() != null) {
filename += rc.getExportFilenamePrefix();
}
if (Strings.isEmpty(reporter.getBatchKey()) == false) {
filename += "_" + reporter.getBatchKey();
}
return filename;
}
private static void export(JRAbstractExporter exporter, JasperPrint printable, String filename, String extension, boolean openAfter) {
try {
exporter.setParameter(JRExporterParameter.JASPER_PRINT, printable);
File file = new File(filename + "." + extension);
exporter.setParameter(JRExporterParameter.OUTPUT_FILE,file );
exporter.exportReport();
if(openAfter && Desktop.isDesktopSupported() && Desktop.getDesktop()!=null){
Desktop.getDesktop().open(file);
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
@Override
public Class<? extends Serializable> getConfigClass() {
return ReporterConfig.class;
}
@Override
public JPanel createConfigEditorPanel(ComponentConfigurationEditorAPI factory,int mode,Serializable config, boolean isFixedIO) {
return new ReporterPanel(factory,(ReporterConfig) config);
}
@Override
public long getFlags(ODLApi api,int mode) {
return ODLComponent.FLAG_ALLOW_USER_INTERACTION_WHEN_RUNNING;
}
// public static void main(String[] args) {
// ReporterConfig config = new ReporterConfig();
// // config.setUseFirstInputTable(true);
// ShowPanel.showPanel(new ReporterComponent().createConfigEditorPanel(null,config, false));
// }
// @Override
// public Iterable<ODLWizardTemplateConfig> getWizardTemplateConfigs(ODLApi api) {
// ODLWizardTemplateConfig conf = new ODLWizardTemplateConfig("Reports", "Reports", "Create reports, exporting to pdf, word, etc...",
// new ReporterConfig()){
//
//// @Override
//// public boolean isScriptFactory(){
//// return true;
//// }
//
// @Override
// public Script createScript(WizardOptionChooseCallback cb,ODLTableDefinition ...tables){
// Script ret = new Script();
//
// ret.setScriptEditorUIType(ScriptEditorType.REPORTER_EDITOR);
//
// // create (currently empty) adapter for the map
// AdapterConfig mapAdapter = new AdapterConfig("Map");
// ret.getAdapters().add(mapAdapter);
//
// // create adapter for the tables
// AdapterConfig reportInput = new AdapterConfig("ReportInput");
// ret.getAdapters().add(reportInput);
// for(ODLTableDefinition table : tables){
// AdaptedTableConfig conf = WizardUtils.createAdaptedTableConfig(table, table.getName());
// reportInput.getTables().add(conf);
// }
//
// // add reporter instruction
// ReporterConfig config = new ReporterConfig();
// InstructionConfig instruction = new InstructionConfig();
// instruction.setComponentConfig(config);
// instruction.setComponent(new ReporterComponent().getId());
// instruction.setDatastore("ReportInput");
// ret.getInstructions().add(instruction);
// return ret;
// }
//
// };
// return Arrays.asList(conf);
//
// }
@Override
public Icon getIcon(ODLApi api,int mode) {
return Icons.loadFromStandardPath("reporter-component.png");
}
@Override
public boolean isModeSupported(ODLApi api,int mode) {
return mode==ODLComponent.MODE_DEFAULT;
}
@Override
public void registerScriptTemplates(final ScriptTemplatesBuilder templatesApi) {
// // old version.. keep for the moment to maintain compatibilty with the tutorial screenshots but get rid of in the future
// templatesApi.registerTemplate("Reports (single screen view)", "Reports (single screen view)", "Create reports, exporting to pdf, word, etc...", new BuildScriptCallback() {
//
// @Override
// public void buildScript(ScriptBuilder builder) {
// builder.setScriptEditorType(ScriptEditorType.REPORTER_EDITOR);
// String mapId = builder.createUniqueAdapterId("Map");
// builder.addDataAdapter(mapId);
//
// String reportInputId = builder.createUniqueAdapterId("ReportInput");
// builder.addDataAdapter(reportInputId);
// for(int i =0 ; i<builder.getSelectedInputTables().getTableCount();i++){
// ODLTableDefinition table = builder.getSelectedInputTables().getTableAt(i);
// builder.addSourcedTableToAdapter(reportInputId, table,table);
// }
// builder.addInstruction(reportInputId, new ReporterComponent().getId(), ODLComponent.MODE_DEFAULT, new ReporterConfig());
//
// }
// });
// new treeview version.
templatesApi.registerTemplate("Reports", "Reports", "Create reports, exporting to pdf, word, etc...", getIODsDefinition(templatesApi.getApi(), new ReporterConfig()),new BuildScriptCallback() {
@Override
public void buildScript(ScriptOption builder) {
// build map adapter and add to top level option
ScriptAdapter mapAdapter = builder.addDataAdapter("Map");
mapAdapter.setName("Image data per row");
mapAdapter.setFlags(mapAdapter.getFlags() | TableFlags.FLAG_IS_DRAWABLES);
String htmlHeader = "<html><body style='width: 300 px'>";
builder.setEditorLabel(htmlHeader + "The reporter component lets you generate reports containing text and map images and export them to pdf, html etc. See the online tutorials for more details.");
// build report input adapter and add to top level option
ScriptAdapter adapter = builder.addDataAdapter("Report content data");
adapter.setName("Report content data");
final String reportInputId = adapter.getAdapterId();
ScriptInputTables inputTables = builder.getInputTables();
for(int i=0 ; i<inputTables.size();i++){
if(inputTables.getSourceTable(i)!=null){
if(Strings.equalsStd(templatesApi.getApi().standardComponents().reporter().getHeaderMapTableName(), inputTables.getTargetTable(i).getName())){
// destination for header map is the drawables table
adapter.addSourcedTableToAdapter(inputTables.getSourceDatastoreId(i),inputTables.getSourceTable(i), inputTables.getTargetTable(i));
}else{
// destination for report is just the same as the source
adapter.addSourcedTableToAdapter(inputTables.getSourceDatastoreId(i),inputTables.getSourceTable(i), inputTables.getSourceTable(i));
}
}
}
ScriptInstruction instruction = builder.addInstruction(reportInputId, getId(), ReporterComponent.MODE_GENERATE_REPORTS, new ReporterConfig());
instruction.setName("Export & processing options");
}
});
// // new treeview version.
// templatesApi.registerTemplate("Reports", "Reports", "Create reports, exporting to pdf, word, etc...", getIODsDefinition(templatesApi.getApi(), new ReporterConfig()),new BuildScriptCallback() {
//
// @Override
// public void buildScript(ScriptOption builder) {
// // build map adapter and add to top level option
// final String mapId = builder.addDataAdapter("Map data per row").getAdapterId();
//
// String htmlHeader = "<html><body style='width: 300 px'>";
// builder.setEditorLabel(htmlHeader + "The reporter component lets you generate reports containing text and map images and export them to pdf, html etc. See the online tutorials for more details.");
//
// // build report input adapter and add to top level option
// ScriptAdapter adapter = builder.addDataAdapter("Reporter input");
// final String reportInputId = adapter.getAdapterId();
//
// ScriptInputTables inputTables = builder.getInputTables();
// for(int i=0 ; i<inputTables.size();i++){
// if(inputTables.getSourceTable(i)!=null){
// if(Strings.equalsStd(ReportConstants.HEADER_MAP_TABLE_NAME, inputTables.getTargetTable(i).getName())){
// // destination for header map is the drawables table
// adapter.addSourcedTableToAdapter(inputTables.getSourceDatastoreId(i),inputTables.getSourceTable(i), inputTables.getTargetTable(i));
// }else{
// // destination for report is just the same as the source
// adapter.addSourcedTableToAdapter(inputTables.getSourceDatastoreId(i),inputTables.getSourceTable(i), inputTables.getSourceTable(i));
// }
// }
// }
//
//
// // build config and add to top level option
// ScriptComponentConfig componentConfig = builder.addComponentConfig("Reporter settings", getId(), new ReporterConfig());
//
// // have three different options
// ScriptOption optionBuilder = builder.addOption("Generate-reports", "Generate reports");
// optionBuilder.addInstruction(reportInputId, getId(), ReporterComponent.MODE_GENERATE_REPORTS, componentConfig.getComponentConfigId());
// optionBuilder.setEditorLabel(htmlHeader + "This is the main report option. This generates a report using the .jasper compiled report template specified in the reporter settings. The report can be automatically exported to pdf, html etc...</html>");
//
// optionBuilder = builder.addOption("View-basic-report-landscape", "View basic report (landscape)");
// optionBuilder.addInstruction(reportInputId, getId(), ReporterComponent.VIEW_BASIC_LANDSCAPE, componentConfig.getComponentConfigId());
// optionBuilder.setEditorLabel(htmlHeader + "Using the data in the report input data adapter, generate a basic landscape report and show it. Use this option when configuring your report input data adapter.</html>");
//
// optionBuilder = builder.addOption("View-basic-report-portrait", "View basic report (portrait)");
// optionBuilder.addInstruction(reportInputId, getId(), ReporterComponent.VIEW_BASIC_PORTRAIT, componentConfig.getComponentConfigId());
// optionBuilder.setEditorLabel(htmlHeader + "Using the data in the report input data adapter, generate a basic portrait report and show it. Use this option when configuring your report input data adapter.</html>");
//
// optionBuilder = builder.addOption("View-map-data-per-row", "View map data per row");
// optionBuilder.addInstruction(mapId, builder.getApi().map().getMapViewerComponentId(), ODLComponent.MODE_DEFAULT);
// optionBuilder.setEditorLabel(htmlHeader + "Use this option to view the map generated by the \"map data per row\" data adapter. "
// + "This will show all drawable objects together in a single map. When using this data adapter in a report you would use one of the image functions to select a subset of the data to show on each row.</html>");
// }
// });
}
@Override
public String getHeaderMapTableName() {
return "Header map";
}
}