/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program 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. * * Copyright (c) 2001 - 2016 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.states.datarow; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.table.TableModel; import org.pentaho.reporting.engine.classic.core.DataFactory; import org.pentaho.reporting.engine.classic.core.DataFactoryDesignTimeSupport; import org.pentaho.reporting.engine.classic.core.DataRow; import org.pentaho.reporting.engine.classic.core.ParameterDataRow; import org.pentaho.reporting.engine.classic.core.ParameterMapping; import org.pentaho.reporting.engine.classic.core.PerformanceTags; import org.pentaho.reporting.engine.classic.core.ReportDataFactoryException; import org.pentaho.reporting.engine.classic.core.ReportProcessingException; import org.pentaho.reporting.engine.classic.core.ResourceBundleFactory; import org.pentaho.reporting.engine.classic.core.event.ReportEvent; import org.pentaho.reporting.engine.classic.core.function.Expression; import org.pentaho.reporting.engine.classic.core.function.ProcessingContext; import org.pentaho.reporting.engine.classic.core.sorting.SortConstraint; import org.pentaho.reporting.engine.classic.core.states.LengthLimitingTableModel; import org.pentaho.reporting.engine.classic.core.states.PerformanceMonitorContext; import org.pentaho.reporting.engine.classic.core.states.QueryDataRowWrapper; import org.pentaho.reporting.engine.classic.core.states.crosstab.CrosstabSpecification; import org.pentaho.reporting.engine.classic.core.util.IntegerCache; import org.pentaho.reporting.engine.classic.core.util.ReportParameterValues; import org.pentaho.reporting.engine.classic.core.wizard.DataSchema; import org.pentaho.reporting.engine.classic.core.wizard.DataSchemaDefinition; import org.pentaho.reporting.libraries.base.util.ArgumentNullException; import org.pentaho.reporting.libraries.base.util.FastStack; import org.pentaho.reporting.libraries.base.util.FormattedMessage; import org.pentaho.reporting.libraries.base.util.PerformanceLoggingStopWatch; public class DefaultFlowController { private static class ReportDataContext { private boolean advanceRequested; protected ReportDataContext( final boolean advanceRequested ) { this.advanceRequested = advanceRequested; } public boolean isAdvanceRequested() { return advanceRequested; } } private MasterDataRow dataRow; private boolean advanceRequested; private FastStack<Integer> expressionsStack; private FastStack<ReportDataContext> dataContextStack; private String exportDescriptor; private ProcessingContext reportContext; private ReportParameterValues parameters; private boolean storedAdvanceRequested; private PerformanceMonitorContext performanceMonitorContext; private boolean isQueryLimitReached; public DefaultFlowController( final ProcessingContext reportContext, final DataSchemaDefinition schemaDefinition, final ReportParameterValues parameters, final PerformanceMonitorContext performanceMonitorContext ) throws ReportDataFactoryException { ArgumentNullException.validate( "performanceMonitorContext", performanceMonitorContext ); if ( reportContext == null ) { throw new NullPointerException(); } if ( parameters == null ) { throw new NullPointerException(); } if ( schemaDefinition == null ) { throw new NullPointerException(); } this.performanceMonitorContext = performanceMonitorContext; this.reportContext = reportContext; this.exportDescriptor = reportContext.getExportDescriptor(); this.expressionsStack = new FastStack<Integer>( 10 ); this.dataContextStack = new FastStack<ReportDataContext>( 10 ); this.advanceRequested = false; this.parameters = parameters; this.dataRow = GlobalMasterRow.createReportRow( reportContext, schemaDefinition, new ParameterDataRow( parameters ) ); } private DefaultFlowController( final DefaultFlowController fc, final MasterDataRow dataRow ) { ArgumentNullException.validate( "fc", fc ); ArgumentNullException.validate( "dataRow", dataRow ); this.performanceMonitorContext = fc.performanceMonitorContext; this.reportContext = fc.reportContext; this.exportDescriptor = fc.exportDescriptor; this.dataContextStack = fc.dataContextStack.clone(); this.expressionsStack = fc.expressionsStack.clone(); this.advanceRequested = fc.advanceRequested; this.storedAdvanceRequested = fc.storedAdvanceRequested; this.dataRow = dataRow; this.parameters = fc.parameters; } public void requireStructuralProcessing() { this.dataRow.requireStructuralProcessing(); } public DefaultFlowController derive() { return new DefaultFlowController( this, dataRow.derive() ); } public DefaultFlowController performAdvance() { if ( dataRow.isAdvanceable() && advanceRequested == false ) { final DefaultFlowController fc = new DefaultFlowController( this, dataRow ); fc.advanceRequested = true; return fc; } return this; } public DefaultFlowController performCommit() { if ( isAdvanceRequested() ) { final DefaultFlowController fc = new DefaultFlowController( this, dataRow ); fc.dataRow = dataRow.advance(); fc.advanceRequested = false; return fc; } return this; } public MasterDataRow getMasterRow() { return dataRow; } public boolean isAdvanceRequested() { return advanceRequested; } /** * This should be called only once per report processing. A JFreeReport object defines the global master report - all * other reports are subreport instances. * <p/> * The global master report receives its parameter set from the Job-Definition, while subreports will read their * parameters from the current datarow state. * * @param query * @param queryLimit * @param queryTimeout * @return * @throws ReportDataFactoryException * @deprecated */ @Deprecated public DefaultFlowController performQuery( final DataFactory dataFactory, final String query, final int queryLimit, final int queryTimeout, final ResourceBundleFactory resourceBundleFactory ) throws ReportDataFactoryException { List<SortConstraint> objects = Collections.emptyList(); return performQuery( dataFactory, query, queryLimit, queryTimeout, resourceBundleFactory, objects ); } public DefaultFlowController performQuery( final DataFactory dataFactory, final String query, final int queryLimit, final int queryTimeout, final ResourceBundleFactory resourceBundleFactory, final List<SortConstraint> sortOrder ) throws ReportDataFactoryException { if ( dataFactory == null ) { throw new NullPointerException(); } if ( resourceBundleFactory == null ) { throw new NullPointerException(); } final MasterDataRow masterRowWithoutData = dataRow .deriveSubDataRow( reportContext, dataFactory, new ParameterDataRow( parameters ), resourceBundleFactory ); final TableModel tableData = performQueryData( masterRowWithoutData.getDataFactory(), query, queryLimit, queryTimeout, masterRowWithoutData .getGlobalView(), false, sortOrder ); final MasterDataRow masterRow = masterRowWithoutData.deriveWithQueryData( tableData ); final DefaultFlowController fc = new DefaultFlowController( this, masterRow ); fc.dataContextStack.push( new ReportDataContext( advanceRequested ) ); fc.dataRow = masterRow; fc.dataRow.resetDataSchema(); return fc; } public DefaultFlowController performDesignTimeQuery( final DataFactory dataFactory, final String query, final int queryLimit, final int queryTimeout, final ResourceBundleFactory resourceBundleFactory ) throws ReportDataFactoryException { if ( dataFactory == null ) { throw new NullPointerException(); } if ( resourceBundleFactory == null ) { throw new NullPointerException(); } final MasterDataRow masterRowWithoutData = dataRow .deriveSubDataRow( reportContext, dataFactory, new ParameterDataRow( parameters ), resourceBundleFactory ); List<SortConstraint> objects = Collections.emptyList(); final TableModel tableData = performQueryData( masterRowWithoutData.getDataFactory(), query, queryLimit, queryTimeout, masterRowWithoutData .getGlobalView(), true, objects ); final MasterDataRow masterRow = masterRowWithoutData.deriveWithQueryData( tableData ); final DefaultFlowController fc = new DefaultFlowController( this, masterRow ); fc.dataContextStack.push( new ReportDataContext( advanceRequested ) ); fc.dataRow = masterRow; fc.dataRow.resetDataSchema(); return fc; } private TableModel performQueryData( final DataFactory dataFactory, final String query, final int queryLimit, final int queryTimeout, final DataRow parameters, final boolean designTime, final List<SortConstraint> sortConstraints ) throws ReportDataFactoryException { if ( dataFactory == null ) { throw new NullPointerException(); } if ( parameters == null ) { throw new NullPointerException(); } if ( query == null ) { return new EmptyTableModel(); } PerformanceLoggingStopWatch sw = performanceMonitorContext.createStopWatch( PerformanceTags.REPORT_QUERY, new FormattedMessage( "query={%s}", query ) ); try { // increasedQueryLimit for reports where queryLimit set. To handle the situation when reportData.getRowCount() == queryLimit int increasedQueryLimit = queryLimit > 0 ? queryLimit + 1 : queryLimit; DataRow params = new QueryDataRowWrapper( parameters, queryTimeout, increasedQueryLimit, sortConstraints ); TableModel reportData; if ( designTime && dataFactory instanceof DataFactoryDesignTimeSupport ) { DataFactoryDesignTimeSupport designTimeSupport = (DataFactoryDesignTimeSupport) dataFactory; reportData = designTimeSupport.queryDesignTimeStructure( query, params ); } else { reportData = dataFactory.queryData( query, params ); } if ( queryLimit > 0 && reportData.getRowCount() >= queryLimit + 1 ) { setQueryLimitReached( true ); return new LengthLimitingTableModel( reportData, queryLimit ); } return reportData; } finally { sw.close(); } } public DefaultFlowController performInitSubreport( final DataFactory dataFactory, final ParameterMapping[] inputParameters, final ResourceBundleFactory resourceBundleFactory ) { if ( dataFactory == null ) { throw new NullPointerException(); } if ( inputParameters == null ) { throw new NullPointerException(); } if ( resourceBundleFactory == null ) { throw new NullPointerException(); } // create a view for the parameters of the report ... final MasterDataRow subReportDataRow; if ( isGlobalImportOrExport( inputParameters ) ) { final ParameterDataRow parameterRow = new ParameterDataRow( dataRow.getGlobalView() ); subReportDataRow = dataRow.deriveSubDataRow( reportContext, dataFactory, parameterRow, resourceBundleFactory ); } else { final ParameterDataRow parameterRow = new ParameterDataRow( inputParameters, dataRow.getGlobalView() ); subReportDataRow = dataRow.deriveSubDataRow( reportContext, dataFactory, parameterRow, resourceBundleFactory ); } final DefaultFlowController fc = new DefaultFlowController( this, subReportDataRow ); fc.dataContextStack.push( new ReportDataContext( advanceRequested ) ); fc.dataRow = subReportDataRow; fc.dataRow.resetDataSchema(); return fc; } @Deprecated public DefaultFlowController performSubReportQuery( final String query, final int queryLimit, final int queryTimeout, final ParameterMapping[] outputParameters ) throws ReportDataFactoryException { List<SortConstraint> con = Collections.emptyList(); return performSubReportQuery( query, queryLimit, queryTimeout, outputParameters, con ); } public DefaultFlowController performSubReportQuery( final String query, final int queryLimit, final int queryTimeout, final ParameterMapping[] outputParameters, final List<SortConstraint> sortConstraints ) throws ReportDataFactoryException { if ( outputParameters == null ) { throw new NullPointerException(); } final MasterDataRow subReportDataRow = this.dataRow; // perform the query ... // add the resultset ... final TableModel tableData = performQueryData( subReportDataRow.getDataFactory(), query, queryLimit, queryTimeout, subReportDataRow .getGlobalView(), false, sortConstraints ); final MasterDataRow masterRow = subReportDataRow.deriveWithQueryData( tableData ); if ( isGlobalImportOrExport( outputParameters ) ) { if ( "true".equals( reportContext.getConfiguration().getConfigProperty( "org.pentaho.reporting.engine.classic.core.EnableGlobalSubReportImports" ) ) ) { masterRow.getParentDataRow().setImportedDataRow( new ImportedVariablesDataRow( masterRow ) ); } else { masterRow.getParentDataRow().setImportedDataRow( new ImportedVariablesDataRow( masterRow, filterGlobalImport( outputParameters ) ) ); } } else { // check and rebuild the parameter mapping from the inner to the outer // context. Only deep-traversal expressions will be able to see these // values (unless they have been defined as local variables). masterRow.getParentDataRow().setImportedDataRow( new ImportedVariablesDataRow( masterRow, outputParameters ) ); } final DefaultFlowController fc = new DefaultFlowController( this, masterRow ); fc.dataContextStack.push( new ReportDataContext( advanceRequested ) ); fc.dataRow = masterRow; fc.dataRow.resetDataSchema(); return fc; } private ParameterMapping[] filterGlobalImport( final ParameterMapping[] parameterMapping ) { final ArrayList<ParameterMapping> filteredList = new ArrayList<ParameterMapping>( parameterMapping.length ); for ( int i = 0; i < parameterMapping.length; i++ ) { final ParameterMapping mapping = parameterMapping[i]; if ( "*".equals( mapping.getName() ) && "*".equals( mapping.getAlias() ) ) { continue; } filteredList.add( mapping ); } return filteredList.toArray( new ParameterMapping[filteredList.size()] ); } /** * Checks whether a global import is defined. A global import effectly overrides all other imports. * * @return true, if there is a global import defined, false otherwise. */ private boolean isGlobalImportOrExport( final ParameterMapping[] inputParameters ) { for ( int i = 0; i < inputParameters.length; i++ ) { final ParameterMapping inputParameter = inputParameters[i]; if ( "*".equals( inputParameter.getName() ) && "*".equals( inputParameter.getAlias() ) ) { return true; } } return false; } public DefaultFlowController activateExpressions( final Expression[] expressions, final boolean preserveState ) throws ReportProcessingException { if ( expressions == null ) { throw new NullPointerException(); } final MasterDataRow dataRow = this.dataRow.derive(); final ExpressionDataRow edr = dataRow.getExpressionDataRow(); edr.pushExpressions( expressions, preserveState ); dataRow.resetDataSchema(); final DefaultFlowController fc = new DefaultFlowController( this, dataRow ); final Integer exCount = IntegerCache.getInteger( expressions.length ); fc.expressionsStack.push( exCount ); return fc; } public DefaultFlowController deactivateExpressions() { final Integer counter = this.expressionsStack.peek(); final int counterRaw = counter.intValue(); if ( counterRaw == 0 ) { final DefaultFlowController fc = new DefaultFlowController( this, dataRow ); fc.expressionsStack.pop(); return fc; } final MasterDataRow dataRow = this.dataRow.derive(); final ExpressionDataRow edr = dataRow.getExpressionDataRow(); final DefaultFlowController fc = new DefaultFlowController( this, dataRow ); fc.expressionsStack.pop(); edr.popExpressions( counterRaw ); dataRow.resetDataSchema(); return fc; } public DefaultFlowController performReturnFromQuery() { final ReportDataRow reportDataRow = dataRow.getReportDataRow(); if ( reportDataRow == null ) { return this; } // We dont close the report data, as some previously saved states may // still reference it. (The caching report data factory takes care of // that later.) final MasterDataRow innerDr = dataRow.deriveWithReturnFromQuery(); final DefaultFlowController fc = new DefaultFlowController( this, innerDr ); final ReportDataContext context = fc.dataContextStack.pop(); fc.dataRow = dataRow.getParentDataRow(); fc.dataRow = fc.dataRow.derive(); fc.advanceRequested = context.isAdvanceRequested(); innerDr.resetDataSchema(); return fc; } public DefaultFlowController performReturnFromSubreport() { // first, we undo the call "performSubreportQuery" and unwrap the report-data (and its corresponding stack entry) final MasterDataRow innerDrFromQuery = dataRow.deriveWithReturnFromQuery(); final DefaultFlowController fc = new DefaultFlowController( this, innerDrFromQuery ); fc.advanceRequested = fc.dataContextStack.pop().isAdvanceRequested(); // second, we undo the call "performInitSubreport" and unwrap the parameter-data (and its corresponding stack entry) final DefaultFlowController fc2 = new DefaultFlowController( fc, dataRow.getParentDataRow().derive() ); fc2.advanceRequested = fc2.dataContextStack.pop().isAdvanceRequested(); return fc2; } public DefaultFlowController performClearExportedParameters() { final MasterDataRow masterDataRow = dataRow.clearExportedParameters(); if ( masterDataRow == dataRow ) { return this; } return new DefaultFlowController( this, dataRow.clearExportedParameters() ); } public String getExportDescriptor() { return exportDescriptor; } public ProcessingContext getReportContext() { return reportContext; } public DefaultFlowController fireReportEvent( final ReportEvent event ) { dataRow.fireReportEvent( event ); return this; } public DataSchema getDataSchema() { return dataRow.getDataSchema(); } public DataFactory getDataFactory() { return dataRow.getDataFactory(); } public DefaultFlowController startCrosstabMode( final CrosstabSpecification crosstabSpecification ) { final MasterDataRow dataRow = this.dataRow.startCrosstabMode( crosstabSpecification ); // begin crosstab mode ... return new DefaultFlowController( this, dataRow ); } public DefaultFlowController endCrosstabMode() { final MasterDataRow dataRow = this.dataRow.endCrosstabMode(); // end crosstab mode ... return new DefaultFlowController( this, dataRow ); } public DefaultFlowController resetRowCursor() { final MasterDataRow dataRow = this.dataRow.resetRowCursor(); return new DefaultFlowController( this, dataRow ); } public DefaultFlowController restart() { final GlobalMasterRow innerDr = dataRow.rebuild(); return new DefaultFlowController( this, innerDr ); } public DefaultFlowController updateDataSchema( final DataSchemaDefinition dataSchemaDefinition ) { return new DefaultFlowController( this, dataRow.updateDataSchema( dataSchemaDefinition ) ); } public DefaultFlowController refreshDataRow() { dataRow.refresh(); return this; } public boolean isCrosstabActive() { return dataRow.isCrosstabActive(); } public DefaultFlowController recordCrosstabRowState() { final MasterDataRow dataRow = this.dataRow.recordCrosstabRowState(); final DefaultFlowController flowController = new DefaultFlowController( this, dataRow ); flowController.storedAdvanceRequested = this.advanceRequested; return flowController; } public DefaultFlowController clearRecordedCrosstabRowState() { final MasterDataRow dataRow = this.dataRow.clearRecordedCrosstabRowState(); return new DefaultFlowController( this, dataRow ); } public DefaultFlowController replayStoredCrosstabRowState() { final MasterDataRow dataRow = this.dataRow.replayStoredCrosstabRowState(); final DefaultFlowController flowController = new DefaultFlowController( this, dataRow ); flowController.advanceRequested = storedAdvanceRequested; return flowController; } public boolean isQueryLimitReached() { return isQueryLimitReached; } public void setQueryLimitReached( boolean queryLimitReached ) { isQueryLimitReached = queryLimitReached; } }