private ProcessState processPaginationLevel(final ProcessState startState,
final PageStateList pageStates,
final int maxRows)
throws ReportProcessingException
{
PerformanceLoggingStopWatch sw = getPerformanceMonitorContext().createStopWatch(PerformanceTags.REPORT_PAGINATE);
try
{
sw.start();
final boolean failOnError = isStrictErrorHandling(getReport().getReportConfiguration());
final ReportProcessingErrorHandler errorHandler = new CollectingReportErrorHandler();
final DefaultLayoutPagebreakHandler pagebreakHandler = new DefaultLayoutPagebreakHandler();
final ProcessState initialReportState = startState.deriveForStorage();
final PageState initialPageState = new PageState(initialReportState, outputProcessor.getPageCursor());
pageStates.add(initialPageState);
final ReportProgressEvent repaginationState = new ReportProgressEvent(this);
// inner loop: process the complete report, calculate the function values
// for the current level. Higher level functions are not available in the
// dataRow.
final int eventTrigger;
if (maxRows <= 0)
{
eventTrigger = Math.max(maxRows / MAX_EVENTS_PER_RUN, MIN_ROWS_PER_EVENT);
}
else
{
eventTrigger = Math.min(maxRows, Math.max(maxRows / MAX_EVENTS_PER_RUN, MIN_ROWS_PER_EVENT));
}
ProcessState state = startState.deriveForStorage();
state.setErrorHandler(errorHandler);
validate(state);
final OutputProcessorMetaData metaData =
state.getFlowController().getReportContext().getOutputProcessorMetaData();
pagebreaksSupported = metaData.isFeatureSupported(OutputProcessorFeature.PAGEBREAKS);
int pageEventCount = 0;
// First and last derive of a page must be a storage derivate - this clones everything and does
// not rely on the more complicated transactional layouting ..
ProcessState fallBackState = pagebreaksSupported ? state.deriveForPagebreak() : null;
ProcessState globalState = pagebreaksSupported ? state.deriveForStorage() : null;
ReportStateKey rollbackPageState = null;
boolean isInRollBackMode = false;
int eventCount = 0;
int lastRow = -1;
while (!state.isFinish())
{
int logPageCount = outputProcessor.getLogicalPageCount();
int physPageCount = outputProcessor.getPhysicalPageCount();
checkInterrupted();
if (lastRow != state.getCurrentRow())
{
lastRow = state.getCurrentRow();
if (eventCount == 0)
{
if (isPagebreaksSupported() && fallBackState != null)
{
repaginationState.reuse(ReportProgressEvent.PAGINATING, fallBackState, calculatePageCount(fallBackState));
}
else
{
repaginationState.reuse(ReportProgressEvent.PAGINATING, state, calculatePageCount(state));
}
fireStateUpdate(repaginationState);
eventCount += 1;
}
else
{
if (eventCount == eventTrigger)
{
eventCount = 0;
}
else
{
eventCount += 1;
}
}
}
// Do not try to layout on a artificial state. Those states are not valid on
// generating page events and cannot be trusted.
ProcessState realFallbackState = fallBackState;
final ProcessState restoreState;
if (pagebreaksSupported && state.isArtifcialState() == false)
{
restoreState = fallBackState;
if (isInRollBackMode == false)
{
if (pageEventCount >= AbstractReportProcessor.COMMIT_RATE)
{
final OutputFunction outputFunction = state.getLayoutProcess().getOutputFunction();
if (outputFunction.createRollbackInformation())
{
realFallbackState = state.deriveForPagebreak();
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Try to generate new fallback state after commit count reached: " + state.getProcessKey());
}
validate(state);
}
else
{
realFallbackState = null;
}
}
}
}
else
{
restoreState = null;
}
final ProcessState nextState = state.advance();
state.setErrorHandler(IgnoreEverythingReportErrorHandler.INSTANCE);
state = nextState;
validate(state);
final ReportStateKey nextStateKey = state.getProcessKey();
if (errorHandler.isErrorOccured() == true)
{
final List childExceptions = Arrays.asList(errorHandler.getErrors());
errorHandler.clearErrors();
final ReportEventException exception =
new ReportEventException("Failed to dispatch an event.", childExceptions);
if (failOnError)
{
throw exception;
}
else
{
AbstractReportProcessor.logger.error("Failed to dispatch an event.", exception);
}
}
if (state.isArtifcialState())
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Silent commit as we are in an artificial state: " + state.getProcessKey());
}
state = state.commit();
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Post Silent commit as we are in an artificial state: " + state.getProcessKey());
}
continue;
}
final OutputFunction outputFunction = state.getLayoutProcess().getOutputFunction();
if (outputFunction instanceof DefaultOutputFunction == false)
{
state = state.commit();
if (state.isFinish() && pageStates.size() == 1)
{
physicalMapping.add(0);
logicalMapping.add(0);
}
continue;
}
final DefaultOutputFunction lm = (DefaultOutputFunction) outputFunction;
final Renderer renderer = lm.getRenderer();
renderer.setStateKey(state.getProcessKey());
pagebreakHandler.setReportState(state);
boolean assertExpectPagebreak = false;
if (isInRollBackMode)
{
if (nextStateKey.equals(rollbackPageState))
{
// reached the border case. We have to insert a manual pagebreak here or at least
// we have to force the renderer to end the page right now.
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
AbstractReportProcessor.logger.debug(
"Paginate: Found real pagebreak position. This might be the last state we process: " + rollbackPageState);
AbstractReportProcessor.logger.debug(
"Paginate: (Current state process key) : " + state.getProcessKey());
AbstractReportProcessor.logger.debug(
"Paginate: (Handler) : " + state.getAdvanceHandler().getClass().getName());
}
assertExpectPagebreak = true;
renderer.addPagebreak();
}
}
final Renderer.LayoutResult pagebreakEncountered = renderer.validatePages();
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
AbstractReportProcessor.logger.debug("Paginate: Validate Page returned " + pagebreakEncountered);
if (assertExpectPagebreak == true && pagebreakEncountered != Renderer.LayoutResult.LAYOUT_PAGEBREAK)
{
AbstractReportProcessor.logger.debug("Paginate: Missed the pagebreak. This smells fishy!");
}
}
if (pagebreakEncountered != Renderer.LayoutResult.LAYOUT_UNVALIDATABLE)
{
if (pagebreaksSupported && state.isArtifcialState() == false)
{
if (isInRollBackMode == false)
{
if (pageEventCount >= AbstractReportProcessor.COMMIT_RATE)
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Try to apply new fallback state after commit count reached: " + state.getProcessKey());
logger.debug("Paginate: : " + renderer.getLastStateKey());
}
fallBackState = realFallbackState;
pageEventCount = 0;
}
else
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Increase counter: " + state.getProcessKey());
}
pageEventCount += 1;
}
}
}
}
else if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
if (pagebreaksSupported && state.isArtifcialState() == false)
{
if (isInRollBackMode == false)
{
logger.debug("Paginate: SKIP : " + state.getProcessKey());
}
}
}
if (pagebreakEncountered == Renderer.LayoutResult.LAYOUT_PAGEBREAK)
{
final boolean onGoingPageBreak;
// renderer.print();
final ReportStateKey lastVisibleStateKey = renderer.getLastStateKey();
if (isPagebreaksSupported() &&
isInRollBackMode == false &&
lastVisibleStateKey != null &&
renderer.isOpen())
{
if (lastVisibleStateKey.equals(nextStateKey) == false &&
lastVisibleStateKey.getSequenceCounter() > globalState.getProcessKey().getSequenceCounter())
{
// Roll back to the last known to be good position and process the states up to, but not
// including the current state. This way, we can fire the page-events *before* this band
// gets printed.
rollbackPageState = lastVisibleStateKey;
final ReportStateKey restoreStateProcessKey = restoreState.getProcessKey();
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
AbstractReportProcessor.logger.debug(
"Paginate: Encountered bad break, need to roll-back: " + rollbackPageState);
AbstractReportProcessor.logger.debug(
"Paginate: Next StateKey : " + state.getProcessKey());
AbstractReportProcessor.logger.debug(
"Paginate: Restored Key : " + restoreStateProcessKey);
AbstractReportProcessor.logger.debug(
"Paginate: Position in event chain : " + restoreState.getSequenceCounter());
}
if (lastVisibleStateKey.getSequenceCounter() < restoreStateProcessKey.getSequenceCounter())
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
AbstractReportProcessor.logger.debug(
"Paginate: Fall back to start of page : " + globalState.getProcessKey());
}
if (lastVisibleStateKey.getSequenceCounter() <= globalState.getProcessKey().getSequenceCounter())
{
throw new ReportProcessingException("Paginate: Error, fallback position is after last visible state.");
}
state = globalState.deriveForStorage();
}
else
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
AbstractReportProcessor.logger.debug(
"Paginate: Fall back to save-state : " + restoreStateProcessKey);
}
state = restoreState.deriveForPagebreak();
}
final DefaultOutputFunction rollbackOutputFunction = (DefaultOutputFunction) state.getLayoutProcess().getOutputFunction();
final Renderer rollbackRenderer = rollbackOutputFunction.getRenderer();
rollbackRenderer.rollback();
validate(state);
isInRollBackMode = true;
fallBackState = null; // there is no way we can fall-back inside a roll-back ..
continue;
}
// when (lastVisibleStateKey.equals(nextStateKey))
// The current state printed content partially on the now finished page and there is more
// content on the currently open page. This is a in-between pagebreak, we invoke a pagebreak
// after this state has been processed.
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
AbstractReportProcessor.logger.debug("Paginate: Encountered on-going break " + lastVisibleStateKey);
}
onGoingPageBreak = true;
rollbackPageState = null;
}
else
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
if (isInRollBackMode)
{
AbstractReportProcessor.logger.debug("Paginate: Encountered a roll-back break: " + isInRollBackMode);
if (assertExpectPagebreak == false)
{
AbstractReportProcessor.logger.debug("Paginate: next state: " + nextStateKey);
if (nextStateKey.equals(rollbackPageState) == false)
{
AbstractReportProcessor.logger.debug("Paginate: rollback state: " + rollbackPageState);
}
}
}
else
{
AbstractReportProcessor.logger.debug("Paginate: Encountered a good break: " + isInRollBackMode);
}
AbstractReportProcessor.logger.debug
("Paginate: : " + state.getProcessKey());
}
isInRollBackMode = false;
rollbackPageState = null;
onGoingPageBreak = false;
}
if (isPagebreaksSupported() == false)
{
// The commit causes all closed-nodes to become finishable. This allows the process-page
// and the incremental-update methods to remove the nodes. For non-streaming targets (where
// pagebreaks are possible) the commit state is managed manually
renderer.applyAutoCommit();
}
if (renderer.processPage(pagebreakHandler, state.getProcessKey(), true) == false)
{
throw new IllegalStateException
("This cannot be. If the validation said we get a new page, how can we now get lost here");
}
if (isPagebreaksSupported())
{
if (renderer.isPendingPageHack() && renderer.isCurrentPageEmpty() == false && renderer.isPageStartPending() == false)
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Delaying next event to allow pending pages to be processed: " + state.getProcessKey());
}
state = PendingPagesHandler.create(state);
}
else
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Adding RestartOnNewPageHandler to open Page in time: " + state.getProcessKey());
}
state = RestartOnNewPageHandler.create(state.commit());
}
}
else
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Commit on page-break: " + state.getProcessKey() + " " + state.getAdvanceHandler().getClass().getName());
}
state = state.commit();
}
if (onGoingPageBreak)
{
renderer.setStateKey(state.getProcessKey());
renderer.addProgressBox();
}
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Post Commit: " + state.getProcessKey() + " " + state.getAdvanceHandler().getClass().getName());
}
// can continue safely ..
final int newLogPageCount = outputProcessor.getLogicalPageCount();
final int newPhysPageCount = outputProcessor.getPhysicalPageCount();
final int result = pageStates.size() - 1;
for (; physPageCount < newPhysPageCount; physPageCount++)
{
physicalMapping.add(result);
}
for (; logPageCount < newLogPageCount; logPageCount++)
{
logicalMapping.add(result);
}
if (state.isFinish() == false)
{
// A pagebreak has occured ...
// We add all but the last state ..
final PageState pageState = new PageState(state, outputProcessor.getPageCursor());
pageStates.add(pageState);
}
if (isPagebreaksSupported())
{
fallBackState = state.deriveForPagebreak();
globalState = state.deriveForStorage();
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Generating new fallback state after pagebreak found: " + state.getProcessKey());
}
pageEventCount = 0;
eventCount = 0;
}
}
else
{
if (isPagebreaksSupported() == false)
{
renderer.applyAutoCommit();
}
// PageEventCount is zero on streaming exports and zero after a new rollback event is created.
if (pageEventCount == 0 && isInRollBackMode == false &&
pagebreakEncountered == Renderer.LayoutResult.LAYOUT_NO_PAGEBREAK)
{
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Perform incremental update: " + state.getProcessKey());
}
renderer.processIncrementalUpdate(false);
}
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Commit: " + state.getProcessKey() + " " + state.getAdvanceHandler().getClass().getName());
}
state = state.commit();
if (AbstractReportProcessor.SHOW_ROLLBACKS)
{
logger.debug("Paginate: Post Commit: " + state.getProcessKey() + " " + state.getAdvanceHandler().getClass().getName());
}
// printLayoutStateToFile(state, false, isInRollBackMode);
if (pagebreaksSupported && fallBackState != restoreState)
{
final DefaultOutputFunction commitableOutputFunction =
(DefaultOutputFunction) state.getLayoutProcess().getOutputFunction();
final Renderer commitableRenderer = commitableOutputFunction.getRenderer();
commitableRenderer.applyRollbackInformation();
}
}
}
return initialReportState;
}
catch (ContentProcessingException e)
{
throw new ReportProcessingException("Content-Processing failed.", e);
}
finally
{
sw.close();
}
}