org.apertereports.engine.ReportMaster Maven / Gradle / Ivy
package org.apertereports.engine;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import net.sf.jasperreports.engine.JRDataSource;
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.JRPropertiesMap;
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.JRMapCollectionDataSource;
import net.sf.jasperreports.engine.export.JRCsvExporter;
import net.sf.jasperreports.engine.export.JRCsvExporterParameter;
import net.sf.jasperreports.engine.export.JRHtmlExporter;
import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
import net.sf.jasperreports.engine.export.JRPdfExporter;
import net.sf.jasperreports.engine.export.JRPdfExporterParameter;
import net.sf.jasperreports.engine.export.JRXlsExporter;
import net.sf.jasperreports.engine.export.JRXlsExporterParameter;
import net.sf.jasperreports.engine.query.JRXPathQueryExecuterFactory;
import org.apache.commons.lang.StringUtils;
import org.apertereports.common.ConfigurationConstants;
import org.apertereports.common.ReportConstants;
import org.apertereports.common.exception.AperteReportsException;
import org.apertereports.common.exception.AperteReportsRuntimeException;
import org.apertereports.common.utils.ReportGeneratorUtils;
import org.apertereports.engine.SubreportProvider.Subreport;
import pl.net.bluesoft.util.lang.StringUtil;
/**
* A workhorse of the Jasper reports engine. This class is responsible for
* generating, exporting and converting JRXMLs to {@link JasperPrint}s and later
* to expected formats.
*
*
* The static methods of this class should be used to convert the report to
* whatever format one wants.
*
* In order to maintain the report generation from a template one should create
* a new instance of this class.
*/
public class ReportMaster implements ReportConstants, ConfigurationConstants {
private static final Logger logger = Logger.getLogger(ReportMaster.class.getName());
private static Pattern subreportPattern = Pattern
.compile("\\<\\!\\[CDATA\\[\\$P\\{[^}]*\\} [^\"]*\"([^\"]*)\\.jasper\"");
private static Pattern jasperReportPattern = Pattern.compile("]+>(\\s+]+>)*",
Pattern.MULTILINE);
private static Pattern subreport_reportElementPattern = Pattern.compile(
"\\s*]+>(\\s*)", Pattern.MULTILINE);
private static String subreportMapParameter = "\n\t";
/**
* Currently processed Jasper report.
*/
private AperteReport report;
/**
* Constructs a new ReportMaster with a given {@link JasperReport}.
*
* @param report
* A {@link JasperReport}
*/
public ReportMaster(AperteReport report) {
super();
this.report = report;
}
/**
* Creates a new ReportMaster instance that omits the {@link ReportCache}
* and compiles the report from source directly.
*
* @param reportSource
* A JRXML report source
* @param subreportProvider
* TODO
* @throws AperteReportsException
* on error
*/
public ReportMaster(String reportSource, SubreportProvider subreportProvider) throws AperteReportsException {
this(reportSource, null, subreportProvider);
}
/**
* Creates a new ReportMaster instance that checks the cache for a compiled
* version of this report. If the compiled report is not found, it creates
* it using a given JRXML report source.
*
* @param reportSource
* A JRXML report source
* @param cacheId
* A report cache id
* @param subreportProvider
* TODO
* @throws JRException
* on Jasper error
* @throws SubreportNotFoundException
*/
public ReportMaster(String reportSource, String cacheId, SubreportProvider subreportProvider)
throws AperteReportsException {
super();
report = compileReport(reportSource, cacheId, subreportProvider);
}
/**
* Creates a new ReportMaster instance that checks the cache for a compiled
* version of this report. If the compiled report is not found, it creates
* it using a given bytes of a JRXML report source.
*
* @param reportSource
* Bytes of a JRXML report source
* @param cacheId
* A report cache id
* @param subreportProvider
* TODO
* @throws AperteReportsException
* on error
*/
public ReportMaster(byte[] reportSource, String cacheId, SubreportProvider subreportProvider)
throws AperteReportsException {
super();
report = compileReport(reportSource, cacheId, subreportProvider);
}
/**
* Exports a {@link JasperPrint} to a desired format. The method also takes
* a customParameters
param. These are included unconditionally
* in the exporter instance used to generate the report.
*
*
* Currently the only configuration parameter that can be handled is
* {@link org.apertereports.common.ConfigurationConstants#JASPER_REPORTS_CHARACTER_ENCODING}
* - the character encoding of the output report.
*
* @param jasperPrint
* A {@link JasperPrint}
* @param format
* Desired output format (i.e. PDF, HTML etc)
* @param exporterParameters
* Additional custom {@link JRExporterParameter} map
* @param configuration
* Configuration parameters
* @return Bytes of a generated report
* @throws AperteReportsException
* on error
*/
public static byte[] exportReport(JasperPrint jasperPrint, String format,
Map exporterParameters, Map configuration)
throws AperteReportsException {
if (configuration == null) {
configuration = new HashMap();
}
if (exporterParameters == null) {
exporterParameters = new HashMap();
}
try {
ReportType outputFormat = ReportType.valueOf(StringUtils.upperCase(format));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
JRExporter exporter;
String characterEncoding = configuration.get(JASPER_REPORTS_CHARACTER_ENCODING);
if (!StringUtil.hasText(characterEncoding)) {
characterEncoding = "Cp1250";
logger.info("Injecting default character encoding: " + characterEncoding);
}
if (outputFormat == ReportType.PDF) {
exporter = new JRPdfExporter();
exporter.setParameter(JRPdfExporterParameter.CHARACTER_ENCODING, characterEncoding);
} else if (outputFormat == ReportType.HTML) {
exporter = new JRHtmlExporter();
exporter.setParameter(JRHtmlExporterParameter.IS_USING_IMAGES_TO_ALIGN, Boolean.FALSE);
exporter.setParameter(JRHtmlExporterParameter.IGNORE_PAGE_MARGINS, Boolean.TRUE);
} else if (outputFormat == ReportType.XLS) {
exporter = new JRXlsExporter();
exporter.setParameter(JRXlsExporterParameter.CHARACTER_ENCODING, characterEncoding);
exporter.setParameter(JRXlsExporterParameter.IGNORE_PAGE_MARGINS, Boolean.TRUE);
exporter.setParameter(JRXlsExporterParameter.IS_DETECT_CELL_TYPE, Boolean.TRUE);
exporter.setParameter(JRXlsExporterParameter.IS_WHITE_PAGE_BACKGROUND, false);
} else if (outputFormat == ReportType.CSV) {
exporter = new JRCsvExporter();
exporter.setParameter(JRCsvExporterParameter.CHARACTER_ENCODING, characterEncoding);
exporter.setParameter(JRCsvExporterParameter.RECORD_DELIMITER, RECORD_DELIMITER);
exporter.setParameter(JRCsvExporterParameter.FIELD_DELIMITER, FIELD_DELIMITER);
} else {
throw new IllegalStateException("Invalid report type. Permitted types are: HTML, PDF, XLS, CSV");
}
if (exporterParameters != null && !exporterParameters.isEmpty()) {
for (Iterator> it = exporterParameters.entrySet().iterator(); it
.hasNext();) {
Map.Entry entry = it.next();
exporter.setParameter(entry.getKey(), entry.getValue());
}
}
exporter.setParameter(JRExporterParameter.JASPER_PRINT_LIST, Collections.singletonList(jasperPrint));
exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, bos);
exporter.exportReport();
return bos.toByteArray();
} catch (JRException e) {
throw new AperteReportsException(ErrorCodes.JASPER_REPORTS_EXCEPTION, e);
}
}
/**
* Exports a report to a desired format. Omits the custom parameters.
*
* @param jasperPrint
* A {@link JasperPrint}
* @param format
* Desired output format (i.e. PDF, HTML etc)
* @param configuration
* Configuration parameters
* @return Bytes of a generated report
* @throws AperteReportsException
* on error
* @see #exportReport(net.sf.jasperreports.engine.JasperPrint, String,
* java.util.Map, java.util.Map)
*/
public static byte[] exportReport(JasperPrint jasperPrint, String format, Map configuration)
throws AperteReportsException {
return exportReport(jasperPrint, format, null, configuration);
}
/**
* Compiles an input JRXML string report source to a {@link JasperPrint}.
* Uses a {@link ReportCache} to cache the compilation.
*
*
* @param reportSource
* A JRXML string source
* @param cacheId
* Report cache id
* @param subreportProvider
* @return Compiled report
* @throws JRException
* on Jasper error
* @throws SubreportNotFoundException
*/
public static AperteReport compileReport(byte[] reportSource, String cacheId, SubreportProvider subreportProvider)
throws AperteReportsException {
return compileReport(reportSource, cacheId, subreportProvider, false);
}
private static AperteReport compileReport(byte[] reportSource, String cacheId, SubreportProvider subreportProvider,
boolean hasParent) throws AperteReportsException {
logger.info("Trying to fetch report '" + cacheId + "' from cache");
AperteReport compiledReport = ReportCache.getReport(cacheId);
Set subreportNames = new HashSet();
if (compiledReport == null) {
logger.info("Report not found. Compiling...");
String source = processSubreports(hasParent, new String(reportSource), subreportNames);
ByteArrayInputStream bis = new ByteArrayInputStream(source.getBytes());
try {
compiledReport = new AperteReport(JasperCompileManager.compileReport(bis));
logger.info("Compiled.");
} catch (JRException e) {
throw new AperteReportsException(ErrorCodes.REPORT_SOURCE_EXCEPTION, e);
}
} else {
logger.info("Report found");
subreportNames.addAll(compiledReport.getSubreports().keySet());
compiledReport.getSubreports().clear();
}
compileSubreports(subreportProvider, compiledReport, subreportNames);
ReportCache.putReport(cacheId, compiledReport);
return compiledReport;
}
private static void compileSubreports(SubreportProvider subreportProvider, AperteReport compiledReport,
Set subreportNames) throws AperteReportsException {
if (subreportNames.size() > 0) {
if (subreportProvider == null) {
subreportProvider = new EmptySubreportProvider();
}
Map subreports = subreportProvider.getSubreports((String[]) subreportNames
.toArray(new String[subreportNames.size()]));
Map compiledSubreports = new HashMap(subreports.size(), 1);
for (Subreport subreport : subreports.values()) {
AperteReport compiledSubreport = compileReport(subreport.getContent(), subreport.getCacheId(),
subreportProvider, true);
compiledSubreports.put(subreport.getName(), compiledSubreport);
}
compiledReport.setSubreports(compiledSubreports);
}
}
public static AperteReport compileReport(String reportSource, String cacheId, SubreportProvider subreportProvider)
throws AperteReportsException {
try {
return compileReport(ReportGeneratorUtils.decodeContent(reportSource), cacheId, subreportProvider);
} catch (UnsupportedEncodingException e) {
throw new AperteReportsRuntimeException(ErrorCodes.UNSUPPORTED_ENCODING, e);
}
}
public byte[] generateAndExportReport(String format, Map reportParameters,
Map exporterParameters, Map configuration, Object dataSource)
throws AperteReportsException {
JasperPrint jasperPrint = generateReport(reportParameters, configuration, dataSource);
return exportReport(jasperPrint, format, exporterParameters, configuration);
}
public byte[] generateAndExportReport(String format, Map reportParameters,
Map exporterParameters, Map configuration)
throws AperteReportsException {
return generateAndExportReport(format, reportParameters, exporterParameters, configuration, null);
}
/**
* Generates and exports a report to the desired format from the source
* passed as a constructor parameter. Returns null
on error.
* The error is noticed by a {@link Logger} instance.
*
*
* @param format
* Output format
* @param reportParameters
* Report parameters
* @param configuration
* Exporter configuration
* @return Bytes of a generated report
*/
public byte[] generateAndExportReport(String format, Map reportParameters,
Map configuration) throws AperteReportsException {
return generateAndExportReport(format, reportParameters, null, configuration);
}
private static String processSubreports(boolean hasParent, String source, Set subreportNames) {
Matcher m = subreportPattern.matcher(source);
while (m.find()) {
String subReportName = m.group(1);
subreportNames.add(subReportName);
source = m
.replaceFirst(""
+ "" + " " + "$1");
}
logger.info(subreportNames.size() + " subreports found");
if (subreportNames.size() > 0 || hasParent) {
m = jasperReportPattern.matcher(source);
m.find();
source = m.replaceFirst(m.group() + subreportMapParameter);
}
return source;
}
/**
* Generates a {@link JasperPrint} using given parameters from the source
* passed as a constructor parameter. Returns null
on error.
* The error is noticed by a {@link Logger} instance.
*
* @param reportParameters
* Report parameters
* @param configuration
* Configuration
* @param dataSource
* Optional data source
* @return Output JasperPrint
*/
public JasperPrint generateReport(Map reportParameters, Map configuration,
Object dataSource) throws AperteReportsException {
try {
JasperPrint jasperPrint = buildJasperPrint(reportParameters, configuration, dataSource);
return jasperPrint;
} catch (Exception e) {
throw new AperteReportsException(e);
}
}
public JasperPrint generateReport(Map reportParameters, Map configuration)
throws AperteReportsException {
return generateReport(reportParameters, configuration, null);
}
public JasperPrint generateReport(Map reportParameters, Object dataSource)
throws AperteReportsException {
return generateReport(reportParameters, new HashMap(), dataSource);
}
public JasperPrint generateReport(Map reportParameters) throws AperteReportsException {
return generateReport(reportParameters, new HashMap());
}
/**
* Gets a list of report parameters derived from the compiled Jasper report.
* Returns null
on error. The error is noticed by a
* {@link Logger} instance.
*
* @return a list of {@link ReportParameter}
*/
public List getParameters() {
if (report == null) {
logger.info("No active report configuration");
return null;
}
JRParameter[] parameters = getJasperReport().getParameters();
List outputList = new ArrayList();
for (JRParameter parameter : parameters) {
ReportParameter outputParameter = new ReportParameter();
outputParameter.setType(parameter.getValueClassName());
outputParameter.setName(parameter.getName());
JRPropertiesMap propertiesMap = parameter.getPropertiesMap();
if (propertiesMap == null || !propertiesMap.hasProperties()) {
continue;
}
String[] propertyNames = propertiesMap.getPropertyNames();
Map outputProperties = new HashMap(propertyNames.length, 1);
for (String propertyName : propertyNames) {
try {
Keys key = Keys.valueOf(StringUtils.upperCase(propertyName));
ReportProperty property = new ReportProperty(key, propertiesMap.getProperty(propertyName));
outputProperties.put(key, property);
} catch (IllegalArgumentException e) {
throw new AperteReportsRuntimeException(propertyName, ErrorCodes.UNKNOWN_PROPERTY_NAME);
}
}
outputParameter.setProperties(outputProperties);
outputList.add(outputParameter);
}
return outputList;
}
/**
* Gets current report's name.
*
* @return The name of the report
*/
public String getReportName() {
return getJasperReport().getName();
}
public AperteReport getAperteReport() {
return report;
}
/**
* Builds a new {@link JasperPrint} of the current report using given
* parameters.
*
* @param reportParameters
* Input report parameters
* @param configuration
* Jasper configuration parameters
* @return A {@link JasperPrint}
* @throws JRException
* on Jasper error
* @throws NamingException
* on errors while accessing the initial context
* @throws SQLException
* on errors while accessing a configured datasource
*/
private JasperPrint buildJasperPrint(Map reportParameters, Map configuration,
Object dataSource) throws JRException, NamingException, SQLException {
if (configuration == null) {
configuration = new HashMap();
}
if (reportParameters == null) {
reportParameters = new HashMap();
}
logger.info("Starting building jasper print");
JasperPrint jasperPrint = null;
injectDefaultValues(reportParameters);
if (report.getSubreports().size() > 0)
reportParameters.put(SUBREPORT_MAP_PARAMETER_NAME, report.getAllNestedSubreports());
Connection connection = null;
try {
if (dataSource == null) {
String jndiDataSource = configuration.get(Parameter.DATASOURCE.name());
connection = jndiDataSource != null ? getConnectionByJNDI(jndiDataSource)
: getConnectionFromReport(getJasperReport());
jasperPrint = connection != null ? JasperFillManager.fillReport(getJasperReport(), reportParameters,
connection) : JasperFillManager.fillReport(getJasperReport(), reportParameters,
new JRMapCollectionDataSource(Collections.singletonList(reportParameters)));
} else {
if (dataSource instanceof Connection) {
jasperPrint = JasperFillManager.fillReport(getJasperReport(), reportParameters,
(Connection) dataSource);
} else if (dataSource instanceof JRDataSource) {
jasperPrint = JasperFillManager.fillReport(getJasperReport(), reportParameters,
(JRDataSource) dataSource);
} else {
throw new AperteReportsRuntimeException(dataSource.getClass().toString(),
ErrorCodes.INVALID_DATASOURCE_TYPE);
}
}
} finally {
if (connection != null) {
connection.close();
}
}
logger.info("Finished building jasper print");
return jasperPrint;
}
/**
* Returns a connection to the configured datasource if the right parameter
* is found.
*
* @param jasperReport
* Input jasper report
* @return The connection to the datasource
* @throws NamingException
* on errors while accessing the initial context
* @throws SQLException
* on errors while connecting to the datasource
*/
private Connection getConnectionFromReport(JasperReport jasperReport) throws NamingException, SQLException {
logger.info("Getting database connection from report");
JRParameter[] parameters = jasperReport.getParameters();
Connection con = null;
for (JRParameter parameter : parameters) {
if (parameter.getName().equalsIgnoreCase(Parameter.DATASOURCE.name())) {
String jndiName = parameter.getDescription();
con = getConnectionByJNDI(jndiName);
break;
}
}
return con;
}
/**
* Tries to lookup a data source from the initial context.
*
* @param jndiName
* Connection JNDI name
* @return Connection to a datasource
* @throws NamingException
* on errors while accessing the initial context
* @throws SQLException
* on errors while connecting to the datasource
*/
private Connection getConnectionByJNDI(String jndiName) throws NamingException, SQLException {
logger.info("Getting database connection by JNDI");
DataSource ds;
try {
ds = (DataSource) new InitialContext().lookup(jndiName);
} catch (Exception e1) {
String prefix = "java:comp/env/"; // possibly tomcat
if (jndiName.matches(prefix + ".*")) {
ds = (DataSource) new InitialContext().lookup(jndiName.substring(prefix.length()));
} else {
ds = (DataSource) new InitialContext().lookup(prefix + jndiName);
}
}
if (ds == null) {
String message = "Unable to lookup datasource: " + jndiName;
logger.info(message);
throw new RuntimeException(message);
}
return ds.getConnection();
}
/**
* Injects default values to parameters that have not been filled.
*
* @param reportParameters
* Input parameters
*/
private void injectDefaultValues(Map reportParameters) {
JRParameter[] originalParameters = getJasperReport().getParameters();
for (JRParameter parameter : originalParameters) {
if (reportParameters.containsKey(parameter.getName())
&& StringUtils.isEmpty(reportParameters.get(parameter.getName()).toString())
&& parameter.getDefaultValueExpression() != null) {
String defaultValue = parameter.getDefaultValueExpression().getText();
if (defaultValue.matches("\".*\"")) {
defaultValue = defaultValue.substring(1, defaultValue.length() - 1);
}
reportParameters.put(parameter.getName(), defaultValue);
}
}
if (!reportParameters.containsKey(JRXPathQueryExecuterFactory.XML_DATE_PATTERN)) {
reportParameters.put(JRXPathQueryExecuterFactory.XML_DATE_PATTERN, DATETIME_PATTERN);
logger.info("Injecting default date format: " + DATETIME_PATTERN);
}
Object locale = reportParameters.get(JRParameter.REPORT_LOCALE);
if (locale != null) {
if (locale instanceof String) {
String[] val = ((String) locale).split("_");
reportParameters.put(JRParameter.REPORT_LOCALE, new Locale(val[0], val[1]));
} else if (!(locale instanceof Locale)) {
locale = null;
}
}
if (locale == null) {
Locale defaultLocale = new Locale("pl", "PL");
reportParameters.put(JRParameter.REPORT_LOCALE, defaultLocale);
logger.info("Unable to find locale parameter. Injecting default locale: " + defaultLocale);
}
}
private JasperReport getJasperReport() {
return report.getJasperReport();
}
}