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");