/*
* 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 - 2013 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 final 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;
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
{
DataRow params = new QueryDataRowWrapper(parameters, queryTimeout, queryLimit, 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)
{
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;
}
}