/* * 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.layout.output; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.ClassicEngineBoot; import org.pentaho.reporting.engine.classic.core.ClassicEngineCoreModule; import org.pentaho.reporting.engine.classic.core.EmptyReportException; import org.pentaho.reporting.engine.classic.core.MasterReport; import org.pentaho.reporting.engine.classic.core.PerformanceTags; import org.pentaho.reporting.engine.classic.core.ReportEventException; import org.pentaho.reporting.engine.classic.core.ReportInterruptedException; import org.pentaho.reporting.engine.classic.core.ReportProcessingException; import org.pentaho.reporting.engine.classic.core.event.ReportProgressEvent; import org.pentaho.reporting.engine.classic.core.event.ReportProgressListener; import org.pentaho.reporting.engine.classic.core.function.OutputFunction; import org.pentaho.reporting.engine.classic.core.layout.AbstractRenderer; import org.pentaho.reporting.engine.classic.core.layout.Renderer; import org.pentaho.reporting.engine.classic.core.states.CollectingReportErrorHandler; import org.pentaho.reporting.engine.classic.core.states.IgnoreEverythingReportErrorHandler; import org.pentaho.reporting.engine.classic.core.states.LayoutProcess; import org.pentaho.reporting.engine.classic.core.states.PerformanceMonitorContext; import org.pentaho.reporting.engine.classic.core.states.ProcessStateHandle; import org.pentaho.reporting.engine.classic.core.states.ReportProcessingErrorHandler; import org.pentaho.reporting.engine.classic.core.states.ReportStateKey; import org.pentaho.reporting.engine.classic.core.states.process.PendingPagesHandler; import org.pentaho.reporting.engine.classic.core.states.process.ProcessState; import org.pentaho.reporting.engine.classic.core.states.process.RestartOnNewPageHandler; import org.pentaho.reporting.engine.classic.core.util.IntList; import org.pentaho.reporting.libraries.base.config.Configuration; import org.pentaho.reporting.libraries.base.util.MemoryUsageMessage; import org.pentaho.reporting.libraries.base.util.PerformanceLoggingStopWatch; import org.pentaho.reporting.libraries.docbundle.DocumentBundle; import org.pentaho.reporting.libraries.docbundle.DocumentMetaData; import org.pentaho.reporting.libraries.docbundle.MemoryDocumentMetaData; import org.pentaho.reporting.libraries.xmlns.common.ParserUtil; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @noinspection HardCodedStringLiteral */ public abstract class AbstractReportProcessor implements ReportProcessor { private static final Log logger = LogFactory.getLog( AbstractReportProcessor.class ); private static final boolean SHOW_ROLLBACKS = logger.isTraceEnabled(); protected static final int MAX_EVENTS_PER_RUN = 200; protected static final int MIN_ROWS_PER_EVENT = 100; protected static final int COMMIT_RATE = 10; /** * A flag defining whether to check for Thread-Interrupts. */ private boolean handleInterruptedState; /** * Storage for listener references. */ private ArrayList<ReportProgressListener> listeners; /** * The listeners as object array for faster access. */ private transient Object[] listenersCache; private MasterReport report; private OutputProcessor outputProcessor; private PageStateList stateList; private transient ProcessStateHandle activeDataFactory; private IntList physicalMapping; private IntList logicalMapping; private boolean paranoidChecks; private PerformanceMonitorContext performanceMonitorContext; /** * A flag controlling whether we save page-states to make browsing pages easier and faster. */ private boolean fullStreamingProcessor; /** * An internal flag that is only valid after the pagination has started. */ private boolean pagebreaksSupported; /** * A flag controlling whether query limit reached. */ private boolean isQueryLimitReached; /** * A flag controlling report interruption * Represents custom interruption mechanism not affected by switching thread interrupted flag */ private boolean manuallyInterrupted; protected AbstractReportProcessor( final MasterReport report, final OutputProcessor outputProcessor ) throws ReportProcessingException { if ( report == null ) { throw new NullPointerException( "Report cannot be null." ); } if ( outputProcessor == null ) { throw new NullPointerException( "OutputProcessor cannot be null" ); } // first cloning ... protect the page layouter function ... // and any changes we may do to the report instance. // a second cloning is done in the start state, to protect the // processed data. this.fullStreamingProcessor = true; this.handleInterruptedState = true; this.outputProcessor = outputProcessor; final Configuration configuration = report.getReportConfiguration(); this.paranoidChecks = "true".equals( configuration .getConfigProperty( "org.pentaho.reporting.engine.classic.core.layout.ParanoidChecks" ) ); final String yieldRateText = configuration.getConfigProperty( "org.pentaho.reporting.engine.classic.core.YieldRate" ); final int yieldRate = ParserUtil.parseInt( yieldRateText, 0 ); if ( yieldRate > 0 ) { addReportProgressListener( new YieldReportListener( yieldRate ) ); } final String profile = configuration.getConfigProperty( "org.pentaho.reporting.engine.classic.core.ProfileReportProcessing" ); if ( "true".equals( profile ) ) { final boolean logLevelProgress = "true".equals( configuration .getConfigProperty( "org.pentaho.reporting.engine.classic.core.performance.LogLevelProgress" ) ); final boolean logPageProgress = "true".equals( configuration .getConfigProperty( "org.pentaho.reporting.engine.classic.core.performance.LogPageProgress" ) ); final boolean logRowProgress = "true".equals( configuration .getConfigProperty( "org.pentaho.reporting.engine.classic.core.performance.LogRowProgress" ) ); addReportProgressListener( new PerformanceProgressLogger( logLevelProgress, logPageProgress, logRowProgress ) ); } final boolean designtime = outputProcessor.getMetaData().isFeatureSupported( OutputProcessorFeature.DESIGNTIME ); this.report = report.derive( designtime ); ReportProcessorThreadHolder.setProcessor( this ); } protected ProcessStateHandle getProcessStateHandle() { return activeDataFactory; } protected MasterReport getReport() { return report; } public OutputProcessor getOutputProcessor() { return outputProcessor; } protected OutputProcessorMetaData getOutputProcessorMetaData() { return outputProcessor.getMetaData(); } private PerformanceMonitorContext getPerformanceMonitorContext() { if ( this.performanceMonitorContext == null ) { this.performanceMonitorContext = ClassicEngineBoot.getInstance().getObjectFactory().get( PerformanceMonitorContext.class ); } return performanceMonitorContext; } /** * Adds a repagination listener. This listener will be informed of pagination events. * * @param l * the listener. */ public void addReportProgressListener( final ReportProgressListener l ) { if ( l == null ) { throw new NullPointerException( "Listener == null" ); } if ( listeners == null ) { listeners = new ArrayList<ReportProgressListener>( 5 ); } listenersCache = null; listeners.add( l ); } /** * Removes a repagination listener. * * @param l * the listener. */ public void removeReportProgressListener( final ReportProgressListener l ) { if ( l == null ) { throw new NullPointerException( "Listener == null" ); } if ( listeners == null ) { return; } listenersCache = null; listeners.remove( l ); } /** * Sends a repagination update to all registered listeners. * * @param state * the state. */ protected void fireStateUpdate( final ReportProgressEvent state ) { if ( listeners == null ) { return; } if ( listenersCache == null ) { listenersCache = listeners.toArray(); } final int length = listenersCache.length; for ( int i = 0; i < length; i++ ) { final ReportProgressListener l = (ReportProgressListener) listenersCache[i]; l.reportProcessingUpdate( state ); } } /** * Sends a repagination update to all registered listeners. * * @param state * the state. */ protected void fireProcessingStarted( final ReportProgressEvent state ) { if ( listeners == null ) { return; } if ( listenersCache == null ) { listenersCache = listeners.toArray(); } final int length = listenersCache.length; for ( int i = 0; i < length; i++ ) { final ReportProgressListener l = (ReportProgressListener) listenersCache[i]; l.reportProcessingStarted( state ); } } /** * Sends a repagination update to all registered listeners. * * @param state * the state. */ protected void fireProcessingFinished( final ReportProgressEvent state ) { if ( listeners == null ) { return; } if ( listenersCache == null ) { listenersCache = listeners.toArray(); } final int length = listenersCache.length; for ( int i = 0; i < length; i++ ) { final ReportProgressListener l = (ReportProgressListener) listenersCache[i]; l.reportProcessingFinished( state ); } } /** * Returns whether the processor should check the threads interrupted state. If this is set to true and the thread was * interrupted, then the report processing is aborted. * * @return true, if the processor should check the current thread state, false otherwise. */ public boolean isHandleInterruptedState() { return handleInterruptedState; } /** * Defines, whether the processor should check the threads interrupted state. If this is set to true and the thread * was interrupted, then the report processing is aborted. * * @param handleInterruptedState * true, if the processor should check the current thread state, false otherwise. */ public void setHandleInterruptedState( final boolean handleInterruptedState ) { this.handleInterruptedState = handleInterruptedState; } /** * Checks whether the current thread is interrupted. * * @throws org.pentaho.reporting.engine.classic.core.ReportInterruptedException * if the thread is interrupted to abort the report processing. */ protected final void checkInterrupted() throws ReportInterruptedException { if ( isHandleInterruptedState() ) { if ( manuallyInterrupted || Thread.currentThread().isInterrupted() ) { throw new ReportInterruptedException( "Current thread [" + Thread.currentThread().getName() + "]is interrupted. Returning." ); } } } public synchronized void close() { if ( activeDataFactory != null ) { this.activeDataFactory.close(); this.activeDataFactory = null; this.stateList = null; this.physicalMapping = null; this.logicalMapping = null; } ReportProcessorThreadHolder.clear(); } public synchronized void cancel() { manuallyInterrupted = true; } public Configuration getConfiguration() { return report.getConfiguration(); } protected DefaultProcessingContext createProcessingContext() throws ReportProcessingException { final OutputProcessorMetaData metaData = getOutputProcessorMetaData(); final MasterReport report = getReport(); final DocumentMetaData documentMetaData; final DocumentBundle bundle = report.getBundle(); if ( bundle != null ) { documentMetaData = bundle.getMetaData(); } else { documentMetaData = new MemoryDocumentMetaData(); } final Integer compatibilityLevel = report.getCompatibilityLevel(); final int cLevel; if ( compatibilityLevel == null ) { cLevel = -1; } else { cLevel = compatibilityLevel; } return new DefaultProcessingContext( metaData, report.getResourceBundleFactory(), report.getConfiguration(), report .getResourceManager(), report.getContentBase(), documentMetaData, report.getReportEnvironment(), cLevel ); } /** * Processes all prepare levels to compute the function values. * * @param state * the state state with which we beginn the processing. * @param maxRows * the number of rows in the table model. * @return the finish state for the current level. * @throws ReportProcessingException * if processing failed or if there are exceptions during the function execution. */ protected ProcessState processPrepareLevels( ProcessState state, final int maxRows ) throws ReportProcessingException { final boolean failOnError = isStrictErrorHandling( getReport().getReportConfiguration() ); final ReportProcessingErrorHandler errorHandler = new CollectingReportErrorHandler(); state.setErrorHandler( errorHandler ); int lastRow = -1; int eventCount = 0; 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 ) ); } final ReportProgressEvent repaginationState = new ReportProgressEvent( this ); // Function processing does not use the PageLayouter, so we don't need // the expensive cloning ... while ( !state.isFinish() ) { checkInterrupted(); if ( lastRow != state.getCurrentRow() ) { lastRow = state.getCurrentRow(); if ( eventCount == 0 ) { repaginationState.reuse( ReportProgressEvent.PRECOMPUTING_VALUES, state, 0 ); fireStateUpdate( repaginationState ); eventCount += 1; } else { if ( eventCount == eventTrigger ) { eventCount = 0; } else { eventCount += 1; } } } // progress = state.createStateProgress(progress); final ProcessState nextState = state.advance(); state.setErrorHandler( IgnoreEverythingReportErrorHandler.INSTANCE ); state = nextState.commit(); if ( errorHandler.isErrorOccured() == true ) { final List childExceptions = Arrays.asList( errorHandler.getErrors() ); errorHandler.clearErrors(); if ( failOnError ) { throw new ReportEventException( "Failed to dispatch an event.", childExceptions ); } else { final ReportEventException exception = new ReportEventException( "Failed to dispatch an event.", childExceptions ); AbstractReportProcessor.logger.error( "Failed to dispatch an event.", exception ); } } } return state; } protected abstract OutputFunction createLayoutManager(); protected void prepareReportProcessing() throws ReportProcessingException { if ( stateList != null ) { // is already paginated. return; } PerformanceLoggingStopWatch sw = getPerformanceMonitorContext().createStopWatch( PerformanceTags.REPORT_PREPARE ); try { sw.start(); // every report processing starts with an StartState. final DefaultProcessingContext processingContext = createProcessingContext(); final MasterReport report = getReport(); final OutputFunction lm = createLayoutManager(); final ProcessState startState = new ProcessState(); try { boolean isQueryLimitReached = startState.initializeForMasterReport( report, processingContext, (OutputFunction) lm.getInstance() ); setQueryLimitReached( isQueryLimitReached ); } finally { activeDataFactory = startState.getProcessHandle(); } ProcessState state = startState; final int maxRows = startState.getNumberOfRows(); // the report processing can be split into 2 separate processes. // The first is the ReportPreparation; all function values are resolved and // a dummy run is done to calculate the final layout. This dummy run is // also necessary to resolve functions which use or depend on the PageCount. // the second process is the printing of the report, this is done in the // processReport() method. processingContext.setPrepareRun( true ); // now process all function levels. // there is at least one level defined, as we added the PageLayouter // to the report. // the levels are defined from +inf to 0 // we don't draw and we do not collect states in a StateList yet final int[] levels; int index; if ( state.isStructuralPreprocessingNeeded() ) { state = performStructuralPreprocessing( state, processingContext ); levels = state.getRequiredRuntimeLevels(); index = 1; } else { levels = state.getRequiredRuntimeLevels(); index = 0; } if ( levels.length == 0 ) { throw new IllegalStateException( "Assertation Failed: No functions defined, invalid implementation." ); } final PerformanceLoggingStopWatch preDataSw = getPerformanceMonitorContext().createStopWatch( PerformanceTags.REPORT_PREPARE_DATA ); try { preDataSw.start(); processingContext.setProgressLevelCount( levels.length ); int level = levels[index]; // outer loop: process all function levels boolean hasNext; do { processingContext.setProcessingLevel( level ); processingContext.setProgressLevel( index ); // if the current level is the output-level, then save the report state. // The state is used later to restart the report processing. if ( level == LayoutProcess.LEVEL_PAGINATE ) { if ( isFullStreamingProcessor() ) { stateList = new FastPageStateList( this ); } else { stateList = new DefaultPageStateList( this ); } physicalMapping = new IntList( 40 ); logicalMapping = new IntList( 20 ); AbstractReportProcessor.logger.debug( "Pagination started .." ); state = processPaginationLevel( state, stateList, maxRows ); } else { preDataSw.start(); state = processPrepareLevels( state, maxRows ); preDataSw.stop( true ); } // if there is an other level to process, then use the finish state to // create a new start state, which will continue the report processing on // the next higher level. hasNext = ( index < ( levels.length - 1 ) ); if ( hasNext ) { index += 1; level = levels[index]; processingContext.setProcessingLevel( level ); processingContext.setProgressLevel( index ); if ( state.isFinish() ) { state = state.restart(); } else { throw new IllegalStateException( "Repaginate did not produce an finish state" ); } } } while ( hasNext == true ); } finally { preDataSw.close(); } // finally return the saved page states. processingContext.setPrepareRun( false ); } finally { sw.close(); } } public void setFullStreamingProcessor( final boolean fullStreamingProcessor ) { this.fullStreamingProcessor = fullStreamingProcessor; } public boolean isFullStreamingProcessor() { return fullStreamingProcessor; } private ProcessState performStructuralPreprocessing( final ProcessState startState, final DefaultProcessingContext processingContext ) throws ReportProcessingException { PerformanceLoggingStopWatch sw = getPerformanceMonitorContext().createStopWatch( PerformanceTags.REPORT_PREPARE_CROSSTAB ); try { sw.start(); processingContext.setProcessingLevel( LayoutProcess.LEVEL_STRUCTURAL_PREPROCESSING ); processingContext.setProgressLevel( -1 ); final int maxRows = startState.getNumberOfRows(); ProcessState state = processPrepareLevels( startState, maxRows ); if ( state.isFinish() ) { state = state.restart(); } else { throw new IllegalStateException( "Repaginate did not produce an finish state" ); } return state; } finally { sw.close(); } } /** * Processes the print level for the current report. This function will fill the report state list while performing * the repagination. * * @param startState * the start state for the print level. * @param pageStates * the list of report states that should receive the created page states. * @param maxRows * the number of rows in the report (used to estaminate the current progress). * @return the finish state for the report. * @throws ReportProcessingException * if there was a problem processing the report. */ 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(); } } @SuppressWarnings( "UnusedDeclaration" ) private void printLayoutStateToFile( final ProcessState state, final boolean print, final boolean inRollBackMode ) { final DefaultOutputFunction of = (DefaultOutputFunction) state.getLayoutProcess().getOutputFunction(); final AbstractRenderer ar = (AbstractRenderer) of.getRenderer(); ar.printLayoutStateToFile( state, print, inRollBackMode ); } protected int calculatePageCount( final ProcessState state ) { final OutputFunction outputFunction = state.getLayoutProcess().getOutputFunction(); if ( outputFunction instanceof DefaultOutputFunction == false ) { return 0; } final DefaultOutputFunction lm = (DefaultOutputFunction) outputFunction; final Renderer renderer = lm.getRenderer(); return renderer.getPageCount() + 1; } private void validate( final ProcessState state ) { if ( paranoidChecks ) { final OutputFunction outputFunction = state.getLayoutProcess().getOutputFunction(); if ( outputFunction instanceof DefaultOutputFunction == false ) { return; } final DefaultOutputFunction of = (DefaultOutputFunction) outputFunction; final Renderer renderer = of.getRenderer(); if ( renderer instanceof AbstractRenderer ) { final AbstractRenderer r = (AbstractRenderer) renderer; r.performParanoidModelCheck(); } } } public boolean isPaginated() { return stateList != null; } protected PageState getLogicalPageState( final int page ) { final int index = logicalMapping.get( page ); final PageState pageState = stateList.get( index ); if ( pageState == null ) { throw new IndexOutOfBoundsException( "The logical mapping between page " + page + " and index " + index + " is invalid." ); } return pageState; } protected PageState getPhysicalPageState( final int page ) { final int index = physicalMapping.get( page ); final PageState pageState = stateList.get( index ); if ( pageState == null ) { throw new IndexOutOfBoundsException( "The physical mapping between page " + page + " and index " + index + " is invalid." ); } return pageState; } /** * Checks whether report processing should be aborted when an exception occurs. * * @param config * the configuration. * @return if strict error handling is enabled. */ protected static boolean isStrictErrorHandling( final Configuration config ) { final String strictError = config.getConfigProperty( ClassicEngineCoreModule.STRICT_ERROR_HANDLING_KEY ); return "true".equals( strictError ); } public PageState processPage( final PageState pageState, final boolean performOutput ) throws ReportProcessingException { if ( pageState == null ) { throw new NullPointerException( "PageState must not be null." ); } final boolean failOnError = isStrictErrorHandling( getReport().getReportConfiguration() ); final ReportProcessingErrorHandler errorHandler = new CollectingReportErrorHandler(); if ( SHOW_ROLLBACKS ) { AbstractReportProcessor.logger.debug( "Process Page: Starting with : " + pageState.getReportState().getProcessKey() ); } try { final ProcessState startState = pageState.getReportState(); outputProcessor.setPageCursor( pageState.getPageCursor() ); final int maxRows = startState.getNumberOfRows(); final ReportProgressEvent repaginationState = new ReportProgressEvent( this ); final DefaultLayoutPagebreakHandler pagebreakHandler = new DefaultLayoutPagebreakHandler(); // 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 ) ); } final boolean pagebreaksSupported = isPagebreaksSupported(); ReportStateKey rollbackPageState = null; ProcessState state = startState.deriveForStorage(); ProcessState fallBackState = pagebreaksSupported ? state.deriveForPagebreak() : null; final ProcessState globalState = pagebreaksSupported ? state.deriveForStorage() : null; state.setErrorHandler( errorHandler ); boolean isInRollBackMode = false; int lastRow = -1; int eventCount = 0; int pageEventCount = 0; while ( !state.isFinish() ) { checkInterrupted(); if ( lastRow != state.getCurrentRow() ) { lastRow = state.getCurrentRow(); if ( eventCount == 0 ) { repaginationState.reuse( ReportProgressEvent.GENERATING_CONTENT, state, calculatePageCount( state ), getLogicalPageCount() ); fireStateUpdate( repaginationState ); eventCount += 1; } else { if ( eventCount == eventTrigger ) { eventCount = 0; } else { eventCount += 1; } } } 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() ) { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Try to generate new fallback state after commit count reached: " + state.getProcessKey() ); } realFallbackState = state.deriveForPagebreak(); } else { realFallbackState = null; } } } } else { restoreState = null; } final ProcessState nextState = state.advance(); state.setErrorHandler( IgnoreEverythingReportErrorHandler.INSTANCE ); state = nextState; final ReportStateKey nextStateKey = state.getProcessKey(); if ( errorHandler.isErrorOccured() == true ) { final List childExceptions = Arrays.asList( errorHandler.getErrors() ); errorHandler.clearErrors(); if ( failOnError ) { throw new ReportEventException( "Failed to dispatch an event.", childExceptions ); } else { final ReportEventException exception = new ReportEventException( "Failed to dispatch an event.", childExceptions ); AbstractReportProcessor.logger.error( "Failed to dispatch an event.", exception ); } } if ( state.isArtifcialState() ) { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Silent commit as we are in an artificial state: " + state.getProcessKey() ); } state = state.commit(); if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Post Silent commit as we are in an artificial state: " + state.getProcessKey() ); } continue; } final OutputFunction outputFunction = state.getLayoutProcess().getOutputFunction(); if ( outputFunction instanceof DefaultOutputFunction == false ) { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Silent commit as we are have no access to the renderer: " + state.getProcessKey() ); } state = state.commit(); 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( "Print: Found real pagebreak position. This might be the last state we process: " + rollbackPageState ); AbstractReportProcessor.logger.debug( "Print: (Current state process key) : " + state.getProcessKey() ); AbstractReportProcessor.logger.debug( "Print: (Handler) : " + state.getAdvanceHandler().getClass().getName() ); } assertExpectPagebreak = true; renderer.addPagebreak(); } } final Renderer.LayoutResult pagebreakEncountered = renderer.validatePages(); if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { AbstractReportProcessor.logger.debug( "Print: Validate Page returned " + pagebreakEncountered ); if ( assertExpectPagebreak == true && pagebreakEncountered != Renderer.LayoutResult.LAYOUT_PAGEBREAK ) { AbstractReportProcessor.logger.debug( "Print: 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( "Print: Try to apply new fallback state after commit count reached: " + state.getProcessKey() ); logger.debug( "Print: : " + renderer.getLastStateKey() ); } fallBackState = realFallbackState; pageEventCount = 0; } else { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Increase counter: " + state.getProcessKey() ); } pageEventCount += 1; } } } } else if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { if ( pagebreaksSupported && state.isArtifcialState() == false ) { if ( isInRollBackMode == false ) { logger.debug( "Print: SKIP : " + state.getProcessKey() ); } } } if ( pagebreakEncountered == Renderer.LayoutResult.LAYOUT_PAGEBREAK ) { final boolean onGoingPageBreak; final ReportStateKey lastVisibleStateKey = renderer.getLastStateKey(); if ( pagebreaksSupported && isInRollBackMode == false && renderer.isOpen() && lastVisibleStateKey != null ) { 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( "Print: Encountered bad break, need to roll-back: " + rollbackPageState ); AbstractReportProcessor.logger.debug( "Print: Next StateKey : " + state.getProcessKey() ); AbstractReportProcessor.logger.debug( "Print: Restored Key : " + restoreStateProcessKey ); AbstractReportProcessor.logger.debug( "Print: Position in event chain : " + restoreState.getSequenceCounter() ); } if ( lastVisibleStateKey.getSequenceCounter() <= restoreStateProcessKey.getSequenceCounter() ) { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { AbstractReportProcessor.logger.debug( "Print: Fall back to start of page : " + globalState.getProcessKey() ); } if ( lastVisibleStateKey.getSequenceCounter() <= globalState.getProcessKey().getSequenceCounter() ) { throw new ReportProcessingException( "Print: Error, fallback position is after last visible state." ); } state = globalState.deriveForStorage(); } else { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { AbstractReportProcessor.logger.debug( "Print: 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; continue; } // 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( "Print: Encountered on-going break " + lastVisibleStateKey ); } onGoingPageBreak = true; rollbackPageState = null; } else { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { if ( isInRollBackMode ) { if ( assertExpectPagebreak == false ) { AbstractReportProcessor.logger.debug( "Print: Encountered a roll-back break: " + isInRollBackMode ); if ( assertExpectPagebreak == false ) { AbstractReportProcessor.logger.debug( "Print: next state: " + nextStateKey ); if ( nextStateKey.equals( rollbackPageState ) == false ) { AbstractReportProcessor.logger.debug( "Print: rollback state: " + rollbackPageState ); } } } } else { AbstractReportProcessor.logger.debug( "Print: Encountered a good break: " + isInRollBackMode ); } AbstractReportProcessor.logger.debug( "Print: : " + state.getProcessKey() ); } onGoingPageBreak = false; } if ( pagebreaksSupported == 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(), performOutput ) == false ) { throw new IllegalStateException( "This must not be." ); } if ( isPagebreaksSupported() ) { if ( renderer.isPendingPageHack() && renderer.isCurrentPageEmpty() == false && renderer.isPageStartPending() == false ) { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Delaying next event to allow pending pages to be processed: " + state.getProcessKey() ); } state = PendingPagesHandler.create( state ); } else { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Adding RestartOnNewPageHandler to open Page in time: " + state.getProcessKey() ); } state = RestartOnNewPageHandler.create( state.commit() ); } } else { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: 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( "Print: Post Commit: " + state.getProcessKey() + " " + state.getAdvanceHandler().getClass().getName() ); } if ( isPagebreaksSupported() ) { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Generating new fallback state after pagebreak found: " + state.getProcessKey() ); } } if ( renderer.isOpen() ) { // No need to create a copy here. It is part of the contract that the resulting page state must be // cloned before it can be used again. The only place where it is used is this method, so we can // be pretty sure that this contract is valid. return new PageState( state, outputProcessor.getPageCursor() ); } } else { if ( pagebreaksSupported == 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 ( pageEventCount == 0 && isInRollBackMode == false && pagebreakEncountered == Renderer.LayoutResult.LAYOUT_NO_PAGEBREAK ) { if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Perform incremental update: " + state.getProcessKey() ); } renderer.processIncrementalUpdate( performOutput ); } if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Commit: " + state.getProcessKey() + " " + state.getAdvanceHandler().getClass().getName() ); } state = state.commit(); if ( AbstractReportProcessor.SHOW_ROLLBACKS ) { logger.debug( "Print: Post Commit: " + state.getProcessKey() + " " + state.getAdvanceHandler().getClass().getName() ); } // printLayoutStateToFile(state, true, isInRollBackMode); if ( pagebreaksSupported && fallBackState != restoreState ) { final DefaultOutputFunction commitableOutputFunction = (DefaultOutputFunction) state.getLayoutProcess().getOutputFunction(); final Renderer commitableRenderer = commitableOutputFunction.getRenderer(); commitableRenderer.applyRollbackInformation(); } } } // We should never reach this point, if this function has been called by the PageStateList. // throw new ReportProcessingException("Content processing failed with an illegal state"); return null; } catch ( ContentProcessingException e ) { throw new ReportProcessingException( "Content-Processing failed.", e ); } } public boolean paginate() throws ReportProcessingException { fireProcessingStarted( new ReportProgressEvent( this ) ); try { if ( isPaginated() == false ) { // Processes the whole report .. prepareReportProcessing(); } } finally { fireProcessingFinished( new ReportProgressEvent( this ) ); } return true; } public void processReport() throws ReportProcessingException { PerformanceLoggingStopWatch swGlobal = getPerformanceMonitorContext().createStopWatch( PerformanceTags.REPORT_PROCESSING ); try { swGlobal.start(); if ( AbstractReportProcessor.logger.isDebugEnabled() ) { AbstractReportProcessor.logger.debug( new MemoryUsageMessage( System.identityHashCode( Thread.currentThread() ) + ": Report processing time: Starting: " ) ); } try { final long startTime = System.currentTimeMillis(); fireProcessingStarted( new ReportProgressEvent( this ) ); if ( isPaginated() == false ) { // Processes the whole report .. prepareReportProcessing(); } PerformanceLoggingStopWatch sw = getPerformanceMonitorContext().createStopWatch( PerformanceTags.REPORT_GENERATE ); try { sw.start(); final long paginateTime = System.currentTimeMillis(); if ( AbstractReportProcessor.logger.isDebugEnabled() ) { AbstractReportProcessor.logger.debug( new MemoryUsageMessage( System.identityHashCode( Thread .currentThread() ) + ": Report processing time: Pagination time: " + ( ( paginateTime - startTime ) / 1000.0 ) ) ); } if ( getLogicalPageCount() == 0 ) { throw new EmptyReportException( "Report did not generate any content." ); } // Start from scratch ... PageState state = getLogicalPageState( 0 ); while ( state != null ) { state = processPage( state, true ); } final long endTime = System.currentTimeMillis(); if ( AbstractReportProcessor.logger.isDebugEnabled() ) { AbstractReportProcessor.logger.debug( new MemoryUsageMessage( System.identityHashCode( Thread .currentThread() ) + ": Report processing time: " + ( ( endTime - startTime ) / 1000.0 ) ) ); } } finally { sw.close(); } } catch ( EmptyReportException re ) { throw re; } catch ( ReportInterruptedException interrupt ) { // log interruption stacktrace in case of debug level only // since interruption is ok. if ( AbstractReportProcessor.logger.isDebugEnabled() ) { AbstractReportProcessor.logger.debug( "Report processing interrupted: " + this.getClass().getName(), interrupt ); } throw interrupt; } catch ( ReportProcessingException re ) { AbstractReportProcessor.logger.error( System.identityHashCode( Thread.currentThread() ) + ": Report processing failed.", re ); throw re; } catch ( Exception e ) { AbstractReportProcessor.logger.error( System.identityHashCode( Thread.currentThread() ) + ": Report processing failed.", e ); throw new ReportProcessingException( "Failed to process the report", e ); } fireProcessingFinished( new ReportProgressEvent( this, getLogicalPageCount(), getLogicalPageCount() ) ); if ( AbstractReportProcessor.logger.isDebugEnabled() ) { AbstractReportProcessor.logger.debug( System.identityHashCode( Thread.currentThread() ) + ": Report processing finished." ); } } finally { swGlobal.close(); } } public int getLogicalPageCount() { if ( logicalMapping == null ) { return -1; } return logicalMapping.size(); } public int getPhysicalPageCount() { if ( physicalMapping == null ) { return -1; } return physicalMapping.size(); } /** * Checks whether the output mode may generate pagebreaks. If we have to deal with pagebreaks, we may have to perform * roll-backs and commits to keep the pagebreaks in sync with the state-processing. This is ugly, expensive and you * better dont try this at home. * <p/> * The roll-back is done for paginated and flow-report outputs, but if we have no autmoatic and manual pagebreaks, * there is no need to even consider to roll-back to a state before the pagebreak (which will never occur). * * @return a flag indicating whether the output target supports pagebreaks. */ private boolean isPagebreaksSupported() { return pagebreaksSupported; } public boolean isQueryLimitReached() { return isQueryLimitReached; } public void setQueryLimitReached( boolean queryLimitReached ) { isQueryLimitReached = queryLimitReached; } }