15. Report Preprocessor
Report Pre-Processors
A report-pre-processor is a specialized function that is executed after the report processing queried the data from the data-factory, but before the report's functions get initialized and the actual report-processing starts. Each master- and sub-report can have its own set of report-processors. The pre-processor is executed only once for each report-definition. At this point in the processing chain, a pre-processor has full control over the layout and data-computation aspects of the report-definition.
A pre-processor can:
create, remove or alter groups
create, remove or reconfigure report-functions
reconfigure existing layout-objects, including to add new subreport, bands or elements or to remove or replace existing ones.A pre-processor can reconfigure the data-sources of any of its report's subreports.
A pre-processor can not:
modify the datasource of the current report
affect the parameters or parameter-processing of the current report
change the report-configuration or influence the layouter-configuration and output-processor
Pre-Processors are specified via the "wizard::pre-processor" attribute on either the master-report or the sub-report. Pre-Processors specified on the master-report will not affect sub-reports and will not be called when a sub-report is executed.
In the reporting system, there are two classes of pre-processors. The pre-processors that can be specified in the report-designer are user-level pre-processors. In addition to these Pre-Processors, system-level Pre-Processors are also used by the reporting engine to optimize the report-processing and to perform necessary data-processing based on the elements found in the report. These automatic Pre-Processors are always executed after the user-level Pre-Processors have run.
Build-in automatic Pre-Processors
org.pentaho.reporting.engine.classic.core.wizard.AggregateFieldPreProcessor
The Aggregate-Field-Pre-Processor introspects all data-elements of a report and activates any aggregation-function that may be specified in the "wizard::aggregation-type" attribute.
org.pentaho.reporting.engine.classic.extensions.legacy.charts.LegacyChartPreProcessor
The Legacy-Chart-Preprocessor extracts a pre-configured chart-expression and one or more chart-data-collector-functions from the report and activates them as regular expressions with an auto-generated name. This is a helper pre-processor to make the legacy-chart-element work.
User-Level Pre-Processors
org.pentaho.reporting.engine.classic.core.DefaultReportPreProcessor
This is a empty Pre-Processor. It returns the report-definition unchanged.This class only serves as base-class for other implementations.
org.pentaho.reporting.engine.classic.core.wizard.RelationalAutoGeneratorPreProcessor
The RelationalAutoGeneratorPreProcessor inspects the result-set of the current data-source and generates an generic banded list-report without any groups. The auto-generated fields will be placed in the itemband and the details-header will be configured to contain suitable labels for these fields.
The target-bands for the pre-processor will follow the wizard-target-selection rules. Therefore a band will be considered a suitable target for generated fields, if it is either the empty root-level band or has the "wizard::generated-content-marker" attribute set to true.
The helper method org.pentaho.reporting.engine.classic.core.wizard.AutoGeneratorUtility#findGeneratedContent can be used in your own implementations to perform such a lookup. The methods org.pentaho.reporting.engine.classic.core.wizard.AutoGeneratorUtility#generateDetailsElement and org.pentaho.reporting.engine.classic.core.wizard.AutoGeneratorUtility#generateHeaderElement are used to generate the fields and header-labels during the processing.
org.pentaho.reporting.engine.classic.wizard.WizardProcessor
The WizardProcessor is the more configurable version of the RelationalAutoGeneratorPreProcessor. The WizardProcessor configures a report based on a wizard-specification-document stored in the report-bundle. The document itself can be edited via the Report-Design-Wizard inside the report-designer. It is also possible to use a Java-API to configure this wizard-specification at runtime.
The wizard-processor can either generate a report from scratch based on the data returned by the data-source and the fields defined in the wizard-specification document or can preserve a previously generated report-definition, so that only the meta-data style and attribute information is refreshed. The "wizard::enable" attribute controls whether a generate ("true") or refresh ("false") strategy is used.
Be aware that to make use of this WizardProcessor at runtime, you have to include "wizard-core" in your runtime classpath.
org.pentaho.reporting.engine.classic.core.modules.misc.bsf.BSFReportPreProcessor
The BSF(Bean-Scripting-Framework)-PreProcessor is a scripted/programmable Pre-Processor. It can be used to prototype custom PreProcessor-implementations. As with all scripting functions, I do not recommend this kind of the functionality for production use if it is used in multiple reports, as it generates report-definitions that are hard to maintain and prone to errors during upgrades. For production use, implement a real Java-Class that can be parametrized from within the report-designer.
The BSHPreProcessor offers access to two variables to perform the pre-processing work.
definition: A instance of "AbstractReportDefinition", either a master-report or a sub-report.
flowController: A org.pentaho.reporting.engine.classic.core.states.datarow.DefaultFlowController, which grants access to the data (via flowController.getMasterRow().getGlobalView()) and the data-schema, which contains all metadata (via flowController.getMasterRow().getDataSchema())
Within the script, you can use either the Classic-Engine-Core API to modify the report-definition or you can create or modify a Wizard-Specification. I recommend to generate a Wizard-Specification when building an AdHoc-report. This way, you can describe the report in higher-level terms and dont have to deal with the detailed styling of the elements.
Note: All examples here are given as BeanShell/Java code. If you use a different language, the exact syntax may differ. In this case, please refer to the BeanScriptingHost documentation and your language's specification for details on the syntax.
For the next few code-examples, a couple of reporting-engine classes are used, and so we have to import them to make the script run. You can find the whole example in the Advanced-Samples" of in the report-designer 3.6.
import java.util.ArrayList;
import org.pentaho.reporting.engine.classic.core.ReportPreProcessor;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.SubReport;
import org.pentaho.reporting.engine.classic.core.wizard.DataSchema;
import org.pentaho.reporting.engine.classic.core.states.datarow.DefaultFlowController;
import org.pentaho.reporting.engine.classic.wizard.WizardProcessorUtil;
import org.pentaho.reporting.engine.classic.wizard.model.WizardSpecification;
import org.pentaho.reporting.engine.classic.wizard.model.DefaultWizardSpecification;
import org.pentaho.reporting.engine.classic.wizard.model.DefaultGroupDefinition;
import org.pentaho.reporting.engine.classic.wizard.model.GroupType;
import org.pentaho.reporting.engine.classic.wizard.model.GroupDefinition;
import org.pentaho.reporting.engine.classic.wizard.model.DefaultDetailFieldDefinition;
import org.pentaho.reporting.engine.classic.wizard.model.DetailFieldDefinition;
To load a wizard-specification document use org.pentaho.reporting.engine.classic.wizard.WizardProcessorUtil#loadWizardSpecification
DefaultWizardSpecification specification = (DefaultWizardSpecification)
WizardProcessorUtil.loadWizardSpecification(definition, definition.getResourceManager());
if (specification == null)
{
specification = new DefaultWizardSpecification();
}
You can now use the data-schema to query the available fields to populate the wizard-specification. The code here assumes that the first two columns will be group fields.
int numberOfGroupFields = 2;
final DataSchema schema = flowController.getDataSchema();
final String[] names = schema.getNames();
final ArrayList groupList = new ArrayList();
for (int i = 0; i < Math.min(numberOfGroupFields, names.length); i++)
{
String name = names[i];
final DefaultGroupDefinition o = new DefaultGroupDefinition();
o.setField(name);
o.setGroupName("Group " + i);
o.setGroupType(GroupType.RELATIONAL);
o.getHeader().setRepeat(true);
groupList.add(o);
}
wizardSpecification.setGroupDefinitions
((GroupDefinition[]) groupList.toArray(new GroupDefinition[groupList.size()]));
final ArrayList detailsList = new ArrayList();
for (int i = 2; i < names.length; i++)
{
String name = names[i];
final DefaultDetailFieldDefinition o = new DefaultDetailFieldDefinition();
o.setField(name);
detailsList.add(o);
}
wizardSpecification.setDetailFieldDefinitions
((DetailFieldDefinition[]) detailsList.toArray(new DetailFieldDefinition[detailsList.size()]));
And finally, after all changes to the wizard-specification have been made, apply the new wizard-specification to the report and return the report-definition.
WizardProcessorUtil.applyWizardSpec(definition, wizardSpecification);
return definition;
Implementing Report-Pre-Processors
Pre-Processors are ordinary Java classes, which have to implement the interface org.pentaho.reporting.engine.classic.core.ReportPreProcessor.
Lets walk through an example, with a pre-processor that has three properties and apparently does nothing at all.
package example;
import org.pentaho.reporting.engine.classic.core.ReportPreProcessor;
import org.pentaho.reporting.engine.classic.core.MasterReport;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.SubReport;
import org.pentaho.reporting.engine.classic.core.states.datarow.DefaultFlowController;
public class SamplePreProcessor implements ReportPreProcessor
{
private String myFieldProperty;
private String myIntProperty;
private String myStringProperty;
public SamplePreProcessor()
{
}
public String getMyFieldProperty()
{
return myFieldProperty;
}
public void setMyFieldProperty(final String myFieldProperty)
{
this.myFieldProperty = myFieldProperty;
}
public String getMyIntProperty()
{
return myIntProperty;
}
public void setMyIntProperty(final String myIntProperty)
{
this.myIntProperty = myIntProperty;
}
public String getMyStringProperty()
{
return myStringProperty;
}
public void setMyStringProperty(final String myStringProperty)
{
this.myStringProperty = myStringProperty;
}
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
public MasterReport performPreProcessing(final MasterReport definition,
final DefaultFlowController flowController) throws ReportProcessingException
{
// yes we should do some work here
return definition;
}
public SubReport performPreProcessing(final SubReport definition,
final DefaultFlowController flowController) throws ReportProcessingException
{
// yes we should do some work here
return definition;
}
}
To make pre-processors visible in the Pentaho-Report-Designer, you have to provide metadata for them at boot-time. The easiest way to ensure this, is to create your own module and to register the pre-processor while the module is initialized.
Pre-Processor metadata should be specified in a XML file called "meta-report-preprocessors.xml" located in your module.
The display names and design-time groupings of the metadata are held in a java.util.ResourceBundle named "example.SamplePreProcessorBundle" (and thus stored in a file called "example/SamplePreProcessorBundle.properties")
pre-processor.org.pentaho.reporting.engine.classic.core.modules.misc.bsf.BSFReportPreProcessor.display-name=BSF-PreProcessor
pre-processor.example.SamplePreProcessor.grouping=Example Corp.
pre-processor.example.SamplePreProcessor.grouping.ordinal=500
pre-processor.example.SamplePreProcessor.ordinal=10
pre-processor.example.SamplePreProcessor.deprecated=
pre-processor.example.SamplePreProcessor.property.myFieldProperty.display-name=My Field
pre-processor.example.SamplePreProcessor.property.myFieldProperty.grouping=Required
pre-processor.example.SamplePreProcessor.property.myFieldProperty.grouping.ordinal=100
pre-processor.example.SamplePreProcessor.property.myFieldProperty.ordinal=30
pre-processor.example.SamplePreProcessor.property.myIntProperty.display-name=Some Integer
pre-processor.example.SamplePreProcessor.property.myIntProperty.grouping=Foo
pre-processor.example.SamplePreProcessor.property.myIntProperty.grouping.ordinal=200
pre-processor.example.SamplePreProcessor.property.myIntProperty.ordinal=30
pre-processor.example.SamplePreProcessor.property.myStringProperty.display-name=A String holding a font-name
pre-processor.example.SamplePreProcessor.property.myStringProperty.grouping=Foo
pre-processor.example.SamplePreProcessor.property.myStringProperty.grouping.ordinal=200
pre-processor.example.SamplePreProcessor.property.myStringProperty.ordinal=50
<meta-data xmlns="http://reporting.pentaho.org/namespaces/engine/classic/metadata/1.0">
<pre-processor class="example.SamplePreProcessor"
bundle-name="example.SamplePreProcessorBundle"
expert="false" hidden="false" preferred="false">
<property name="myFieldProperty" value-role="Field"/>
<property name="myIntProperty"/>
<property name="myStringProperty" propertyEditor="org.pentaho.reporting.engine.classic.core.metadata.propertyeditors.FontFamilyPropertyEditor"/>
</pre-processor>
</meta-data>
Within the module-initializer, you now can register the Pre-processor via
ElementMetaDataParser.initializeOptionalReportPreProcessorMetaData
("example/meta-report-preprocessors.xml");