/* * 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.layout; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.Band; import org.pentaho.reporting.engine.classic.core.Group; import org.pentaho.reporting.engine.classic.core.GroupBody; import org.pentaho.reporting.engine.classic.core.InvalidReportStateException; import org.pentaho.reporting.engine.classic.core.PerformanceTags; import org.pentaho.reporting.engine.classic.core.ReportDefinition; import org.pentaho.reporting.engine.classic.core.ReportProcessingException; import org.pentaho.reporting.engine.classic.core.function.ExpressionRuntime; import org.pentaho.reporting.engine.classic.core.function.ProcessingContext; import org.pentaho.reporting.engine.classic.core.layout.build.LayoutModelBuilder; import org.pentaho.reporting.engine.classic.core.layout.build.RenderModelBuilder; import org.pentaho.reporting.engine.classic.core.layout.build.ReportRenderModelBuilder; import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox; import org.pentaho.reporting.engine.classic.core.layout.output.ContentProcessingException; import org.pentaho.reporting.engine.classic.core.layout.output.LayoutPagebreakHandler; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessor; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature; import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData; import org.pentaho.reporting.engine.classic.core.layout.output.ValidateSafeToStoreStateStep; import org.pentaho.reporting.engine.classic.core.layout.process.ApplyAutoCommitStep; import org.pentaho.reporting.engine.classic.core.layout.process.ApplyCachedValuesStep; import org.pentaho.reporting.engine.classic.core.layout.process.ApplyCommitStep; import org.pentaho.reporting.engine.classic.core.layout.process.CanvasMajorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.CanvasMinorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.CommitStep; import org.pentaho.reporting.engine.classic.core.layout.process.ComputeStaticPropertiesProcessStep; import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMajorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMinorAxisLayoutStep; import org.pentaho.reporting.engine.classic.core.layout.process.ParagraphLineBreakStep; import org.pentaho.reporting.engine.classic.core.layout.process.RollbackStep; import org.pentaho.reporting.engine.classic.core.layout.process.TableValidationStep; import org.pentaho.reporting.engine.classic.core.layout.process.ValidateModelStep; import org.pentaho.reporting.engine.classic.core.states.PerformanceMonitorContext; import org.pentaho.reporting.engine.classic.core.states.ReportStateKey; import org.pentaho.reporting.engine.classic.core.states.process.ProcessState; import org.pentaho.reporting.engine.classic.core.util.InstanceID; import org.pentaho.reporting.libraries.base.util.ArgumentNullException; import org.pentaho.reporting.libraries.base.util.PerformanceLoggingStopWatch; /** * The LayoutSystem is a simplified version of the LibLayout-rendering system. * * @author Thomas Morgner * @noinspection HardCodedStringLiteral */ public abstract class AbstractRenderer implements Renderer { private class CloseListener implements ChangeListener { public void stateChanged( final ChangeEvent e ) { close(); } } private static final Log logger = LogFactory.getLog( AbstractRenderer.class ); private RenderModelBuilder renderModelBuilder; private ValidateModelStep validateModelStep; private ComputeStaticPropertiesProcessStep staticPropertiesStep; private ParagraphLineBreakStep paragraphLineBreakStep; private InfiniteMinorAxisLayoutStep minorAxisLayoutStep; private CanvasMinorAxisLayoutStep canvasMinorAxisLayoutStep; private InfiniteMajorAxisLayoutStep majorAxisLayoutStep; private CanvasMajorAxisLayoutStep canvasMajorAxisLayoutStep; private ValidateSafeToStoreStateStep validateSafeToStoreStateStep; private TableValidationStep tableValidationStep; private CommitStep commitStep; private ApplyCommitStep applyCommitStep; private RollbackStep rollbackStep; private ApplyAutoCommitStep applyAutoCommitStep; private OutputProcessor outputProcessor; private int pagebreaks; private boolean dirty; private ReportStateKey lastStateKey; private ApplyCachedValuesStep applyCachedValuesStep; private boolean readOnly; private boolean paranoidChecks; private boolean wrapProgressMarkerInSection; private LayoutResult lastValidateResult; private PerformanceLoggingStopWatch validateStopWatch; private PerformanceLoggingStopWatch paginateStopWatch; private PerformanceMonitorContext performanceMonitorContext; protected AbstractRenderer( final OutputProcessor outputProcessor ) { this.outputProcessor = outputProcessor; this.validateModelStep = new ValidateModelStep(); this.staticPropertiesStep = new ComputeStaticPropertiesProcessStep(); this.paragraphLineBreakStep = new ParagraphLineBreakStep(); this.minorAxisLayoutStep = new InfiniteMinorAxisLayoutStep(); this.canvasMinorAxisLayoutStep = new CanvasMinorAxisLayoutStep(); this.majorAxisLayoutStep = new InfiniteMajorAxisLayoutStep(); this.canvasMajorAxisLayoutStep = new CanvasMajorAxisLayoutStep(); this.validateSafeToStoreStateStep = new ValidateSafeToStoreStateStep(); this.applyCachedValuesStep = new ApplyCachedValuesStep(); this.commitStep = new CommitStep(); this.applyAutoCommitStep = new ApplyAutoCommitStep(); this.applyCommitStep = new ApplyCommitStep(); this.rollbackStep = new RollbackStep(); this.tableValidationStep = new TableValidationStep(); } protected void initialize() { this.renderModelBuilder = createRenderModelBuilder(); } protected ReportRenderModelBuilder createRenderModelBuilder() { return new ReportRenderModelBuilder( createComponentFactory() ); } protected RenderComponentFactory createComponentFactory() { return new DefaultRenderComponentFactory(); } public LayoutModelBuilder getNormalFlowLayoutModelBuilder() { return renderModelBuilder.getNormalFlowLayoutModelBuilder(); } public int getPageCount() { return 0; } protected RenderModelBuilder getRenderModelBuilder() { return renderModelBuilder; } protected LogicalPageBox getPageBox() { return renderModelBuilder.getPageBox(); } public boolean isSafeToStore() { final LogicalPageBox pageBox = getPageBox(); if ( pageBox == null ) { return true; } return validateSafeToStoreStateStep.isSafeToStore( pageBox ); } protected OutputProcessorMetaData getMetaData() { return getOutputProcessor().getMetaData(); } public void setStateKey( final ReportStateKey stateKey ) { renderModelBuilder.updateStateKey( stateKey ); } public OutputProcessor getOutputProcessor() { return outputProcessor; } protected boolean isWidowOrphanDefinitionsEncountered() { return staticPropertiesStep.isWidowOrphanDefinitionsEncountered(); } public void startReport( final ReportDefinition report, final ProcessingContext processingContext, final PerformanceMonitorContext performanceMonitorContext ) { ArgumentNullException.validate( "report", report ); ArgumentNullException.validate( "processingContext", processingContext ); ArgumentNullException.validate( "performanceMonitorContext", performanceMonitorContext ); if ( readOnly ) { throw new IllegalStateException(); } this.performanceMonitorContext = performanceMonitorContext; this.performanceMonitorContext.addChangeListener( new CloseListener() ); this.validateStopWatch = performanceMonitorContext.createStopWatch( PerformanceTags.REPORT_LAYOUT_VALIDATE ); this.paginateStopWatch = performanceMonitorContext.createStopWatch( PerformanceTags.REPORT_LAYOUT_PROCESS ); this.majorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext ); this.canvasMajorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext ); this.minorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext ); this.canvasMinorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext ); outputProcessor.processingStarted( report, processingContext ); initializeRendererOnStartReport( processingContext ); renderModelBuilder.startReport( report, processingContext ); markDirty(); } protected void initializeRendererOnStartReport( final ProcessingContext processingContext ) { final OutputProcessorMetaData metaData = getMetaData(); this.paranoidChecks = "true".equals( metaData.getConfiguration().getConfigProperty( "org.pentaho.reporting.engine.classic.core.layout.ParanoidChecks" ) ) && processingContext.getOutputProcessorMetaData().isFeatureSupported( OutputProcessorFeature.DESIGNTIME ) == false; this.wrapProgressMarkerInSection = "true".equals( metaData.getConfiguration().getConfigProperty( "org.pentaho.reporting.engine.classic.core.legacy.WrapProgressMarkerInSection" ) ); staticPropertiesStep.initialize( metaData, processingContext ); canvasMinorAxisLayoutStep.initialize( metaData, processingContext ); minorAxisLayoutStep.initialize( metaData ); canvasMajorAxisLayoutStep.initialize( metaData ); majorAxisLayoutStep.initialize( metaData ); } public void startSubReport( final ReportDefinition report, final InstanceID insertationPoint ) { if ( readOnly ) { throw new IllegalStateException( "Renderer is marked read-only" ); } renderModelBuilder.startSubReport( report, insertationPoint ); } public void startGroup( final Group group, final Integer predictedStateCount ) { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.startGroup( group, 5 ); } public void startGroupBody( final GroupBody groupBody, final Integer predictedStateCount ) { if ( logger.isDebugEnabled() ) { logger.debug( "Group-Body: Predicted size: " + predictedStateCount ); } if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.startGroupBody( groupBody, predictedStateCount ); markDirty(); } public void startSection( final SectionType type ) { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.startSection( type ); } public InlineSubreportMarker[] endSection() { if ( readOnly ) { throw new IllegalStateException(); } // todo: Cheap hack for now. Convert this into a real check that figures out whether real changes have been done. final RenderModelBuilder.SectionResult result = renderModelBuilder.endSection(); if ( result.isEmpty() == false ) { markDirty(); } return result.getSubreportMarkers(); } public void endGroupBody() { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.endGroupBody(); markDirty(); } public void endGroup() { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.endGroup(); markDirty(); } public void endSubReport() { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.endSubReport(); markDirty(); } public void endReport() { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.endReport(); markDirty(); } public void addEmptyRootLevelBand() throws ReportProcessingException { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.addEmptyRootLevelBand(); } public void addProgressBox() throws ReportProcessingException { if ( readOnly ) { throw new IllegalStateException(); } if ( wrapProgressMarkerInSection ) { renderModelBuilder.startSection( SectionType.NORMALFLOW ); renderModelBuilder.addProgressBox(); renderModelBuilder.endSection(); } else { renderModelBuilder.addProgressBox(); } } public void add( final Band band, final ExpressionRuntime runtime ) throws ReportProcessingException { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.add( runtime, band ); } public void addToNormalFlow( final Band band, final ExpressionRuntime runtime ) throws ReportProcessingException { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.addToNormalFlow( runtime, band ); } public LayoutResult validatePages() throws ContentProcessingException { if ( readOnly ) { throw new IllegalStateException(); } try { validateStopWatch.start(); final LogicalPageBox pageBox = getPageBox(); if ( pageBox == null ) { // StartReport has not been called yet .. lastValidateResult = LayoutResult.LAYOUT_UNVALIDATABLE; return LayoutResult.LAYOUT_UNVALIDATABLE; } if ( !dirty && lastValidateResult != null ) { return lastValidateResult; } setLastStateKey( null ); setPagebreaks( 0 ); if ( validateModelStep.isLayoutable( pageBox ) == false ) { // STRUCT if ( logger.isDebugEnabled() ) { logger.debug( "Content-Ref# " + pageBox.getContentRefCount() ); } lastValidateResult = LayoutResult.LAYOUT_UNVALIDATABLE; return LayoutResult.LAYOUT_UNVALIDATABLE; } // These structural processors will skip old nodes. These beasts cannot be cached otherwise. tableValidationStep.validate( pageBox ); // STRUCT paragraphLineBreakStep.compute( pageBox ); // STRUCT staticPropertiesStep.compute( pageBox ); // STRUCT minorAxisLayoutStep.compute( pageBox ); // VISUAL canvasMinorAxisLayoutStep.compute( pageBox ); // VISUAL majorAxisLayoutStep.compute( pageBox ); // VISUAL canvasMajorAxisLayoutStep.compute( pageBox ); // VISUAL if ( preparePagination( pageBox ) == false ) { return LayoutResult.LAYOUT_UNVALIDATABLE; } applyCachedValuesStep.compute( pageBox ); // STRUCT if ( isPageFinished() ) { lastValidateResult = LayoutResult.LAYOUT_PAGEBREAK; return LayoutResult.LAYOUT_PAGEBREAK; } else { lastValidateResult = LayoutResult.LAYOUT_NO_PAGEBREAK; return LayoutResult.LAYOUT_NO_PAGEBREAK; } } finally { validateStopWatch.stop( true ); } } protected boolean preparePagination( final LogicalPageBox pageBox ) { return true; } protected void clearDirty() { dirty = false; } protected abstract boolean isPageFinished(); public void processIncrementalUpdate( final boolean performOutput ) throws ContentProcessingException { // dirty = false; } public boolean processPage( final LayoutPagebreakHandler handler, final Object commitMarker, final boolean performOutput ) throws ContentProcessingException { if ( readOnly ) { throw new IllegalStateException(); } try { paginateStopWatch.start(); final LogicalPageBox pageBox = getPageBox(); if ( pageBox == null ) { // StartReport has not been called yet .. // Log.debug ("PageBox null"); return false; } if ( dirty == false ) { // Log.debug ("Not dirty"); return false; } setLastStateKey( null ); setPagebreaks( 0 ); if ( validateModelStep.isLayoutable( pageBox ) == false ) { logger.debug( "Not layoutable" ); return false; } // processes the current page boolean repeat = true; while ( repeat ) { if ( handler != null ) { // make sure we generate an up-to-date page-footer. This also implies that there // are more page-finished than page-started events generated during the report processing. handler.pageFinished(); } if ( outputProcessor.getMetaData().isFeatureSupported( OutputProcessorFeature.PAGEBREAKS ) ) { createRollbackInformation(); applyRollbackInformation(); performParanoidModelCheck(); } tableValidationStep.validate( pageBox ); // STRUCT paragraphLineBreakStep.compute( pageBox ); // STRUCT staticPropertiesStep.compute( pageBox ); // VISUAL minorAxisLayoutStep.compute( pageBox ); canvasMinorAxisLayoutStep.compute( pageBox ); majorAxisLayoutStep.compute( pageBox ); canvasMajorAxisLayoutStep.compute( pageBox ); if ( preparePagination( pageBox ) == false ) { return ( pagebreaks > 0 ); } applyCachedValuesStep.compute( pageBox ); repeat = performPagination( handler, performOutput ); } clearDirty(); return ( pagebreaks > 0 ); } finally { validateStopWatch.stop( isOpen() ); paginateStopWatch.stop( isOpen() ); } } protected abstract boolean performPagination( LayoutPagebreakHandler handler, final boolean performOutput ) throws ContentProcessingException; /** * A hook to allow easier debugging. * * @param pageBox * the current page box. * @noinspection NoopMethodInAbstractClass */ protected void debugPrint( final LogicalPageBox pageBox ) { } public ReportStateKey getLastStateKey() { return lastStateKey; } protected void setLastStateKey( final ReportStateKey lastStateKey ) { this.lastStateKey = lastStateKey; } protected void setPagebreaks( final int pagebreaks ) { this.pagebreaks = pagebreaks; } public int getPagebreaks() { return pagebreaks; } public boolean isOpen() { final LogicalPageBox pageBox = getPageBox(); if ( pageBox == null ) { return false; } return pageBox.isOpen(); } public boolean isValid() { return readOnly == false; } public Renderer deriveForStorage() { try { final AbstractRenderer renderer = (AbstractRenderer) clone(); renderer.readOnly = false; renderer.renderModelBuilder = renderModelBuilder.deriveForStorage(); return renderer; } catch ( CloneNotSupportedException cne ) { throw new InvalidReportStateException( "Failed to derive Renderer", cne ); } } public Renderer deriveForPagebreak() { try { final AbstractRenderer renderer = (AbstractRenderer) clone(); renderer.readOnly = true; renderer.renderModelBuilder = renderModelBuilder.deriveForPageBreak(); return renderer; } catch ( CloneNotSupportedException cne ) { throw new InvalidReportStateException( "Failed to derive Renderer", cne ); } } public void performParanoidModelCheck() { if ( paranoidChecks ) { renderModelBuilder.performParanoidModelCheck(); } } public Object clone() throws CloneNotSupportedException { return super.clone(); } public void addPagebreak() { if ( readOnly ) { throw new IllegalStateException(); } renderModelBuilder.addPageBreak(); } public boolean clearPendingPageStart( final LayoutPagebreakHandler layoutPagebreakHandler ) { // intentionally left empty. return false; } public boolean isCurrentPageEmpty() { return false; } public boolean isPageStartPending() { return false; } public boolean isDirty() { return dirty; } public void createRollbackInformation() { final LogicalPageBox pageBox = getPageBox(); if ( pageBox != null ) { commitStep.compute( pageBox ); } } public void applyRollbackInformation() { final LogicalPageBox pageBox = getPageBox(); if ( pageBox != null ) { applyCommitStep.compute( pageBox ); } } public void validateAfterCommit() { if ( paranoidChecks ) { renderModelBuilder.validateAfterCommit(); } } public void rollback() { readOnly = false; final LogicalPageBox pageBox = getPageBox(); if ( pageBox != null ) { rollbackStep.compute( pageBox ); renderModelBuilder.restoreStateAfterRollback(); validateAfterCommit(); } } public void applyAutoCommit() { final LogicalPageBox pageBox = getPageBox(); if ( pageBox != null ) { applyAutoCommitStep.compute( pageBox ); } } public boolean isPendingPageHack() { return false; } protected void markDirty() { dirty = true; lastValidateResult = null; } public void print() { if ( renderModelBuilder.getPageBox() == null ) { logger.info( "Printing impossible - Page-Box empty" ); } else { ModelPrinter.INSTANCE.print( renderModelBuilder.getPageBox() ); } } public void newPageStarted() { if ( logger.isDebugEnabled() ) { logger.debug( "================================ CLEAR HEADER AND FOOTER ==================================: " + getPageCount() ); } final LogicalPageBox pageBox = getPageBox(); pageBox.getFooterArea().clear(); pageBox.getRepeatFooterArea().clear(); pageBox.getHeaderArea().clear(); pageBox.getWatermarkArea().clear(); } /** * This is a debug helper function. It is not used in normal report runs. It helps debug layouter states and the * roll-back system by dumping all layouts into a directory on the file system for automated diffs. * * @param state * @param print * @param rollback */ @SuppressWarnings( "UnusedDeclaration" ) public void printLayoutStateToFile( final ProcessState state, final boolean print, final boolean rollback ) { /* * if (((state.getSequenceCounter() <= 14440 || state.getSequenceCounter() >= 14445)) || (state.getSequenceCounter() * % 1) != 0) { return; } */ String fileName = "test-output/" + state.getSequenceCounter(); fileName += print ? "-print" : "-paginate"; fileName += rollback ? "-rb" : ""; fileName += ".xml"; FileModelPrinter.print( fileName, getPageBox() ); } protected PerformanceLoggingStopWatch getValidateStopWatch() { return validateStopWatch; } protected PerformanceLoggingStopWatch getPaginateStopWatch() { return paginateStopWatch; } protected PerformanceMonitorContext getPerformanceMonitorContext() { return performanceMonitorContext; } protected void close() { this.majorAxisLayoutStep.close(); this.canvasMajorAxisLayoutStep.close(); this.minorAxisLayoutStep.close(); this.canvasMinorAxisLayoutStep.close(); validateStopWatch.close(); paginateStopWatch.close(); } }