net.sf.jasperreports.engine.export.JRCsvMetadataExporter Maven / Gradle / Ivy
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2016 TIBCO Software Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports 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 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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 JasperReports. If not, see .
*/
package net.sf.jasperreports.engine.export;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.jasperreports.annotations.properties.Property;
import net.sf.jasperreports.annotations.properties.PropertyScope;
import net.sf.jasperreports.engine.DefaultJasperReportsContext;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRPrintText;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.util.JRStringUtil;
import net.sf.jasperreports.engine.util.JRStyledText;
import net.sf.jasperreports.export.CsvExporterConfiguration;
import net.sf.jasperreports.export.CsvMetadataExporterConfiguration;
import net.sf.jasperreports.export.CsvMetadataReportConfiguration;
import net.sf.jasperreports.properties.PropertyConstants;
/**
* Exports a JasperReports document to CSV format based on the metadata provided.
*
* The exporter allows users to specify which columns should be included in the CSV export, what other value than the default
* should they contain and whether the values for some columns should be auto filled when they are empty or missing (e.g. value
* for group columns)
*
* @author Narcis Marcu ([email protected])
*/
public class JRCsvMetadataExporter extends JRAbstractCsvExporter
{
/**
* Property specifying the name of the column that should appear in the CSV export.
* It must be one of the values in {@link CsvMetadataReportConfiguration#getColumnNames()}, if provided.
*
* @see JRPropertiesUtil
*/
@Property(
category = PropertyConstants.CATEGORY_EXPORT,
scopes = {PropertyScope.TEXT_ELEMENT},
sinceVersion = PropertyConstants.VERSION_4_0_0
)
public static final String PROPERTY_COLUMN_NAME = JRPropertiesUtil.PROPERTY_PREFIX + "export.csv.column.name";
/**
* Property that specifies whether the value associated with {@link #PROPERTY_COLUMN_NAME PROPERTY_COLUMN_NAME} should be repeated or not
* when it is missing.
*
* The property itself defaults to false
.
*
*
* @see JRPropertiesUtil
*/
@Property(
category = PropertyConstants.CATEGORY_EXPORT,
defaultValue = PropertyConstants.BOOLEAN_FALSE,
scopes = {PropertyScope.TEXT_ELEMENT},
sinceVersion = PropertyConstants.VERSION_4_0_0,
valueType = Boolean.class
)
public static final String PROPERTY_REPEAT_VALUE = JRPropertiesUtil.PROPERTY_PREFIX + "export.csv.repeat.value";
/**
* Property that specifies what value to associate with {@link #PROPERTY_COLUMN_NAME PROPERTY_COLUMN_NAME}.
*
* The property itself defaults to the text value of the report element that this property is assigned to.
*
*
* @see JRPropertiesUtil
*/
@Property(
category = PropertyConstants.CATEGORY_EXPORT,
scopes = {PropertyScope.TEXT_ELEMENT},
sinceVersion = PropertyConstants.VERSION_4_0_0
)
public static final String PROPERTY_DATA = JRPropertiesUtil.PROPERTY_PREFIX + "export.csv.data";
/**
*
*/
protected List columnNames;
boolean isFirstRow = true;
protected class ExporterContext extends BaseExporterContext implements JRCsvExporterContext
{
}
/**
* @see #JRCsvMetadataExporter(JasperReportsContext)
*/
public JRCsvMetadataExporter()
{
this(DefaultJasperReportsContext.getInstance());
}
/**
*
*/
public JRCsvMetadataExporter(JasperReportsContext jasperReportsContext)
{
super(jasperReportsContext);
exporterContext = new ExporterContext();
}
@Override
protected Class getConfigurationInterface()
{
return CsvMetadataExporterConfiguration.class;
}
@Override
protected Class getItemConfigurationInterface()
{
return CsvMetadataReportConfiguration.class;
}
@Override
@SuppressWarnings("deprecation")
protected void ensureOutput()
{
if (exporterOutput == null)
{
exporterOutput =
new net.sf.jasperreports.export.parameters.ParametersWriterExporterOutput(
getJasperReportsContext(),
getParameters(),
getCurrentJasperPrint()
);
}
}
@Override
protected void exportPage(JRPrintPage page) throws IOException
{
List elements = page.getElements();
Map currentRow = new HashMap();
Map repeatedValues = new HashMap();
CsvMetadataReportConfiguration configuration = getCurrentItemConfiguration();
boolean hasDefinedColumns = columnNames != null; // if columns where passed in as property
exportElements(elements, configuration, currentRow, repeatedValues, hasDefinedColumns);
// write last row
if (columnNames != null && columnNames.size() > 0)
{
// write header if it was not yet written
if (isFirstRow && configuration.isWriteHeader())
{
writeReportHeader();
}
writeCurrentRow(currentRow, repeatedValues);
}
JRExportProgressMonitor progressMonitor = configuration.getProgressMonitor();
if (progressMonitor != null)
{
progressMonitor.afterPageExport();
}
}
protected void exportElements(
List elements,
CsvMetadataReportConfiguration configuration,
Map currentRow,
Map repeatedValues,
boolean hasDefinedColumns) throws IOException
{
for (int i = 0; i < elements.size(); ++i)
{
Object element = elements.get(i);
if (element instanceof JRPrintText)
{
exportText((JRPrintText) element, configuration, currentRow, repeatedValues, hasDefinedColumns);
}
else if (element instanceof JRPrintFrame)
{
exportElements(((JRPrintFrame) element).getElements(), configuration, currentRow, repeatedValues, hasDefinedColumns);
}
}
}
protected void exportText(
JRPrintText textElement,
CsvMetadataReportConfiguration configuration,
Map currentRow,
Map repeatedValues,
boolean hasDefinedColumns) throws IOException
{
String currentTextValue = null;
if (textElement.getPropertiesMap().getPropertyNames().length > 0)
{
String currentColumnName = textElement.getPropertiesMap().getProperty(PROPERTY_COLUMN_NAME);
String currentColumnData = textElement.getPropertiesMap().getProperty(PROPERTY_DATA);
boolean repeatValue = getPropertiesUtil().getBooleanProperty(textElement, PROPERTY_REPEAT_VALUE, false);
if (textElement.getPropertiesMap().containsProperty(PROPERTY_DATA))
{
currentTextValue = currentColumnData;
} else
{
JRStyledText styledText = getStyledText(textElement);
if (styledText != null)
{
currentTextValue = styledText.getText();
} else
{
currentTextValue = "";
}
}
// when no columns are provided, build the column names list as they are retrieved from the report element property
if (!hasDefinedColumns)
{
if (columnNames == null)
{
columnNames = new ArrayList();
}
if (currentColumnName != null && currentColumnName.length() > 0 && !columnNames.contains(currentColumnName))
{
columnNames.add(currentColumnName);
}
}
if (columnNames.size() > 0)
{
if (columnNames.contains(currentColumnName) && !currentRow.containsKey(currentColumnName) && isColumnReadOnTime(currentRow, currentColumnName)) // the column is for export but was not read yet and comes in the expected order
{
currentRow.put(currentColumnName, currentTextValue);
} else if ( (columnNames.contains(currentColumnName) && !currentRow.containsKey(currentColumnName) && !isColumnReadOnTime(currentRow, currentColumnName)) // the column is for export, was not read yet, but it is read after it should be
|| (columnNames.contains(currentColumnName) && currentRow.containsKey(currentColumnName)) ) // the column is for export and was already read
{
// write header
if (isFirstRow && configuration.isWriteHeader())
{
writeReportHeader();
}
isFirstRow = false;
writeCurrentRow(currentRow, repeatedValues);
currentRow.clear();
currentRow.put(currentColumnName, currentTextValue);
}
// set auto fill columns
if (repeatValue && currentColumnName != null && currentColumnName.length() > 0 && currentTextValue.length() > 0)
{
repeatedValues.put(currentColumnName, currentTextValue);
}
}
}
}
@Override
protected void initExport()
{
super.initExport();
}
@Override
protected void initReport()
{
super.initReport();
CsvMetadataReportConfiguration configuration = getCurrentItemConfiguration();
columnNames = JRStringUtil.split(configuration.getColumnNames(), ",");
isFirstRow = true;
}
/**
* Writes the delimiter-separated column names
*/
protected void writeReportHeader() throws IOException
{
CsvExporterConfiguration configuration = getCurrentConfiguration();
String fieldDelimiter = configuration.getFieldDelimiter();
String recordDelimiter = configuration.getRecordDelimiter();
StringBuilder rowBuilder = new StringBuilder();
for (int i = 0; i < columnNames.size(); i++)
{
rowBuilder.append(columnNames.get(i));
if (i < (columnNames.size()-1))
{
rowBuilder.append(fieldDelimiter);
} else
{
rowBuilder.append(recordDelimiter);
}
}
if (rowBuilder.length() > 0)
{
writer.write(rowBuilder.toString());
}
}
/**
* Writes the current row.
*
* If the row is empty, nothing is written. If the are columns for auto fill (with valid data), they will be set on the current
* row and the row will be written only if it was not originally empty. This prevents the export file from having rows just with auto filled data.
*
* @param currentRow
* @param repeatedValues
* @throws IOException
*/
protected void writeCurrentRow(Map currentRow, Map repeatedValues) throws IOException
{
// FIXME: the rows that are incomplete (e.g. in case of a group, there are rows that contain only the group columns
// because the report spanned over a new page and it contains only the values for the group columns, as header, and other information
// that is not for export, like counts or totals) should not be written
CsvExporterConfiguration configuration = getCurrentConfiguration();
String fieldDelimiter = configuration.getFieldDelimiter();
String recordDelimiter = configuration.getRecordDelimiter();
StringBuilder rowBuilder = new StringBuilder();
boolean isEmptyRow = true;
for (int i = 0; i < columnNames.size(); i++)
{
String currentTextValue = currentRow.get(columnNames.get(i));
if (currentTextValue != null && currentTextValue.length() > 0)
{
isEmptyRow = false;
rowBuilder.append(prepareText(currentTextValue));
} else
{
String repeatedValue = repeatedValues.get(columnNames.get(i));
if (repeatedValue != null && repeatedValue.length() > 0)
{
rowBuilder.append(prepareText(repeatedValue));
}
}
if (i < (columnNames.size()-1))
{
rowBuilder.append(fieldDelimiter);
} else
{
rowBuilder.append(recordDelimiter);
}
}
if (!isEmptyRow)
{
writer.write(rowBuilder.toString());
}
}
/**
* Compares the highest index of the currentRow's columns with the index of the column to be inserted
* to determine if the current column is read in the proper order
*
* @param currentRow
* @param currentColumnName
*/
private boolean isColumnReadOnTime(Map currentRow, String currentColumnName)
{
int indexOfLastFilledColumn = -1;
Set currentlyFilledColumns = currentRow.keySet();
for (String column: currentlyFilledColumns)
{
indexOfLastFilledColumn = Math.max(indexOfLastFilledColumn, columnNames.indexOf(column));
}
return indexOfLastFilledColumn < columnNames.indexOf(currentColumnName);
}
}