0001: /**
0002: * ===========================================
0003: * JFreeReport : a free Java reporting library
0004: * ===========================================
0005: *
0006: * Project Info: http://reporting.pentaho.org/
0007: *
0008: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
0009: *
0010: * This library is free software; you can redistribute it and/or modify it under the terms
0011: * of the GNU Lesser General Public License as published by the Free Software Foundation;
0012: * either version 2.1 of the License, or (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
0015: * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0016: * See the GNU Lesser General Public License for more details.
0017: *
0018: * You should have received a copy of the GNU Lesser General Public License along with this
0019: * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
0020: * Boston, MA 02111-1307, USA.
0021: *
0022: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0023: * in the United States and other countries.]
0024: *
0025: * ------------
0026: * AbstractReportProcessor.java
0027: * ------------
0028: * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
0029: */package org.jfree.report.layout.output;
0030:
0031: import java.util.ArrayList;
0032: import java.util.Arrays;
0033: import java.util.Date;
0034: import java.util.List;
0035:
0036: import org.jfree.base.config.ModifiableConfiguration;
0037: import org.jfree.base.log.MemoryUsageMessage;
0038: import org.jfree.report.DataFactory;
0039: import org.jfree.report.EmptyReportException;
0040: import org.jfree.report.JFreeReport;
0041: import org.jfree.report.ReportDataFactoryException;
0042: import org.jfree.report.ReportEventException;
0043: import org.jfree.report.ReportInterruptedException;
0044: import org.jfree.report.ReportProcessingException;
0045: import org.jfree.report.event.ReportProgressEvent;
0046: import org.jfree.report.event.ReportProgressListener;
0047: import org.jfree.report.function.OutputFunction;
0048: import org.jfree.report.layout.AbstractRenderer;
0049: import org.jfree.report.layout.DefaultLayoutSupport;
0050: import org.jfree.report.layout.Renderer;
0051: import org.jfree.report.states.CollectingReportErrorHandler;
0052: import org.jfree.report.states.IgnoreEverythingReportErrorHandler;
0053: import org.jfree.report.states.LayoutProcess;
0054: import org.jfree.report.states.ReportProcessingErrorHandler;
0055: import org.jfree.report.states.ReportState;
0056: import org.jfree.report.states.ReportStateKey;
0057: import org.jfree.report.states.StateUtilities;
0058: import org.jfree.report.states.process.ProcessState;
0059: import org.jfree.report.util.IntList;
0060: import org.jfree.report.util.ReportConfigurationUtil;
0061: import org.jfree.report.util.StringUtil;
0062: import org.jfree.util.Log;
0063:
0064: /**
0065: * Creation-Date: 08.04.2007, 14:52:52
0066: *
0067: * @author Thomas Morgner
0068: */
0069: public abstract class AbstractReportProcessor implements
0070: ReportProcessor {
0071: private static final boolean SHOW_ROLLBACKS = false;
0072:
0073: protected static final int MAX_EVENTS_PER_RUN = 200;
0074: protected static final int MIN_ROWS_PER_EVENT = 100;
0075: protected static final int PAGE_EVENT_RATE = 20;
0076:
0077: /**
0078: * A flag defining whether to check for Thread-Interrupts.
0079: */
0080: private boolean handleInterruptedState;
0081:
0082: /**
0083: * Storage for listener references.
0084: */
0085: private ArrayList listeners;
0086:
0087: /**
0088: * The listeners as object array for faster access.
0089: */
0090: private transient Object[] listenersCache;
0091:
0092: private JFreeReport report;
0093:
0094: private OutputProcessor outputProcessor;
0095:
0096: private PageStateList stateList;
0097: private transient DataFactory activeDataFactory;
0098:
0099: private IntList physicalMapping;
0100: private IntList logicalMapping;
0101: private boolean pagebreaksSupported;
0102: private boolean paranoidChecks;
0103:
0104: protected AbstractReportProcessor(final JFreeReport report,
0105: final OutputProcessor outputProcessor)
0106: throws ReportProcessingException {
0107: if (report == null) {
0108: throw new NullPointerException("Report cannot be null.");
0109: }
0110:
0111: if (outputProcessor == null) {
0112: throw new NullPointerException(
0113: "OutputProcessor cannot be null");
0114: }
0115:
0116: try {
0117: // first cloning ... protect the page layouter function ...
0118: // and any changes we may do to the report instance.
0119:
0120: // a second cloning is done in the start state, to protect the
0121: // processed data.
0122: this .report = (JFreeReport) report.clone();
0123: } catch (CloneNotSupportedException cne) {
0124: throw new ReportProcessingException(
0125: "Initial Clone of Report failed");
0126: }
0127:
0128: this .handleInterruptedState = true;
0129: this .outputProcessor = outputProcessor;
0130: this .paranoidChecks = "true".equals(outputProcessor
0131: .getMetaData().getConfiguration().getConfigProperty(
0132: "org.jfree.report.layout.ParanoidChecks"));
0133: this .pagebreaksSupported = outputProcessor.getMetaData()
0134: .isFeatureSupported(OutputProcessorFeature.PAGEBREAKS);
0135: final ModifiableConfiguration configuration = report
0136: .getReportConfiguration();
0137: final String yieldRateText = configuration
0138: .getConfigProperty("org.jfree.report.YieldRate");
0139: final int yieldRate = StringUtil.parseInt(yieldRateText, 0);
0140: if (yieldRate > 0) {
0141: addReportProgressListener(new YieldReportListener(yieldRate));
0142: }
0143: }
0144:
0145: protected JFreeReport getReport() {
0146: return report;
0147: }
0148:
0149: public OutputProcessor getOutputProcessor() {
0150: return outputProcessor;
0151: }
0152:
0153: protected OutputProcessorMetaData getOutputProcessorMetaData() {
0154: return outputProcessor.getMetaData();
0155: }
0156:
0157: /**
0158: * Adds a repagination listener. This listener will be informed of pagination events.
0159: *
0160: * @param l the listener.
0161: */
0162: public synchronized void addReportProgressListener(
0163: final ReportProgressListener l) {
0164: if (l == null) {
0165: throw new NullPointerException("Listener == null");
0166: }
0167: if (listeners == null) {
0168: listeners = new ArrayList(5);
0169: }
0170: listenersCache = null;
0171: listeners.add(l);
0172: }
0173:
0174: /**
0175: * Removes a repagination listener.
0176: *
0177: * @param l the listener.
0178: */
0179: public synchronized void removeReportProgressListener(
0180: final ReportProgressListener l) {
0181: if (l == null) {
0182: throw new NullPointerException("Listener == null");
0183: }
0184: if (listeners == null) {
0185: return;
0186: }
0187: listenersCache = null;
0188: listeners.remove(l);
0189: }
0190:
0191: /**
0192: * Sends a repagination update to all registered listeners.
0193: *
0194: * @param state the state.
0195: */
0196: protected synchronized void fireStateUpdate(
0197: final ReportProgressEvent state) {
0198: if (listeners == null) {
0199: return;
0200: }
0201: if (listenersCache == null) {
0202: listenersCache = listeners.toArray();
0203: }
0204: final int length = listenersCache.length;
0205: for (int i = 0; i < length; i++) {
0206: final ReportProgressListener l = (ReportProgressListener) listenersCache[i];
0207: l.reportProcessingUpdate(state);
0208: }
0209: }
0210:
0211: /**
0212: * Returns whether the processor should check the threads interrupted state. If this is set to true and the thread was
0213: * interrupted, then the report processing is aborted.
0214: *
0215: * @return true, if the processor should check the current thread state, false otherwise.
0216: */
0217: public boolean isHandleInterruptedState() {
0218: return handleInterruptedState;
0219: }
0220:
0221: /**
0222: * Defines, whether the processor should check the threads interrupted state. If this is set to true and the thread
0223: * was interrupted, then the report processing is aborted.
0224: *
0225: * @param handleInterruptedState true, if the processor should check the current thread state, false otherwise.
0226: */
0227: public void setHandleInterruptedState(
0228: final boolean handleInterruptedState) {
0229: this .handleInterruptedState = handleInterruptedState;
0230: }
0231:
0232: /**
0233: * Checks, whether the current thread is interrupted.
0234: *
0235: * @throws org.jfree.report.ReportInterruptedException
0236: * if the thread is interrupted to abort the report processing.
0237: */
0238: protected final void checkInterrupted()
0239: throws ReportInterruptedException {
0240: if (isHandleInterruptedState() && Thread.interrupted()) {
0241: throw new ReportInterruptedException(
0242: "Current thread signaled interrupt; Aborting report processing.");
0243: }
0244: }
0245:
0246: public synchronized void close() {
0247: if (activeDataFactory != null) {
0248: this .activeDataFactory.close();
0249: this .activeDataFactory = null;
0250: this .stateList = null;
0251: this .physicalMapping = null;
0252: this .logicalMapping = null;
0253: }
0254: }
0255:
0256: protected DefaultProcessingContext createProcessingContext() {
0257: final OutputProcessorMetaData metaData = getOutputProcessorMetaData();
0258: final boolean maxLineHeightUsed = metaData
0259: .isFeatureSupported(OutputProcessorFeature.LEGACY_LINEHEIGHT_CALC) == false;
0260: final boolean imageResolutionMapping = metaData
0261: .isFeatureSupported(OutputProcessorFeature.IMAGE_RESOLUTION_MAPPING) == false;
0262: final JFreeReport report = getReport();
0263: return new DefaultProcessingContext(metaData
0264: .getExportDescriptor(), new DefaultLayoutSupport(
0265: maxLineHeightUsed, imageResolutionMapping), report
0266: .getResourceBundleFactory(), report.getConfiguration(),
0267: report.getResourceManager(), report.getContentBase());
0268: }
0269:
0270: /**
0271: * Processes all prepare levels to compute the function values.
0272: *
0273: * @param state the state state with which we beginn the processing.
0274: * @param level the current processing level.
0275: * @param maxRows the number of rows in the table model.
0276: * @return the finish state for the current level.
0277: * @throws ReportProcessingException if processing failed or if there are exceptions during the function execution.
0278: */
0279: protected ProcessState processPrepareLevels(ProcessState state,
0280: final int level, final int maxRows)
0281: throws ReportProcessingException {
0282: final boolean failOnError = ReportConfigurationUtil
0283: .isStrictErrorHandling(getReport()
0284: .getReportConfiguration());
0285: final ReportProcessingErrorHandler errorHandler = new CollectingReportErrorHandler();
0286: state.setErrorHandler(errorHandler);
0287:
0288: int lastRow = -1;
0289: int eventCount = 0;
0290: final int eventTrigger = Math.min(maxRows / MAX_EVENTS_PER_RUN,
0291: MIN_ROWS_PER_EVENT);
0292: final ReportProgressEvent repaginationState = new ReportProgressEvent(
0293: this );
0294: // Function processing does not use the PageLayouter, so we don't need
0295: // the expensive cloning ...
0296: while (!state.isFinish()) {
0297: checkInterrupted();
0298: if (lastRow != state.getCurrentDataItem()) {
0299: lastRow = state.getCurrentDataItem();
0300: if (eventCount == 0) {
0301: repaginationState.reuse(level, state
0302: .getCurrentDataItem(), state
0303: .getNumberOfRows(), state.getCurrentPage(),
0304: state.getProgressLevel(), state
0305: .getProgressLevelCount());
0306: fireStateUpdate(repaginationState);
0307: eventCount += 1;
0308: } else {
0309: if (eventCount == eventTrigger) {
0310: eventCount = 0;
0311: } else {
0312: eventCount += 1;
0313: }
0314: }
0315: }
0316:
0317: //progress = state.createStateProgress(progress);
0318: final ProcessState nextState = state.advance();
0319: state
0320: .setErrorHandler(IgnoreEverythingReportErrorHandler.INSTANCE);
0321: state = nextState.commit();
0322:
0323: if (errorHandler.isErrorOccured() == true) {
0324: final List childExceptions = Arrays.asList(errorHandler
0325: .getErrors());
0326: errorHandler.clearErrors();
0327: if (failOnError) {
0328: throw new ReportEventException(
0329: "Failed to dispatch an event.",
0330: childExceptions);
0331: } else {
0332: final ReportEventException exception = new ReportEventException(
0333: "Failed to dispatch an event.",
0334: childExceptions);
0335: Log
0336: .error("Failed to dispatch an event.",
0337: exception);
0338: }
0339: }
0340: }
0341: return state;
0342: }
0343:
0344: protected abstract DefaultOutputFunction createLayoutManager();
0345:
0346: protected void prepareReportProcessing()
0347: throws ReportProcessingException {
0348: if (stateList != null) {
0349: // is already paginated.
0350: return;
0351: }
0352:
0353: final long start = System.currentTimeMillis();
0354:
0355: try {
0356: // every report processing starts with an StartState.
0357: final DefaultProcessingContext processingContext = createProcessingContext();
0358: final JFreeReport report = getReport();
0359: report.setProperty(JFreeReport.REPORT_DATE_PROPERTY,
0360: new Date());
0361: report.setProperty(JFreeReport.REPORT_LAYOUT_SUPPORT,
0362: processingContext.getLayoutSupport());
0363:
0364: final OutputFunction lm = createLayoutManager();
0365:
0366: final LayoutProcess lp = new LayoutProcess(
0367: (OutputFunction) lm.getInstance(), report
0368: .getStructureFunctions());
0369:
0370: final ProcessState startState = new ProcessState(report,
0371: processingContext, lp);
0372: activeDataFactory = startState.getDataFactory();
0373: ProcessState state = startState;
0374: final int maxRows = startState.getNumberOfRows();
0375:
0376: report.getDataFactory().open();
0377:
0378: // the report processing can be splitted into 2 separate processes.
0379: // The first is the ReportPreparation; all function values are resolved and
0380: // a dummy run is done to calculate the final layout. This dummy run is
0381: // also necessary to resolve functions which use or depend on the PageCount.
0382:
0383: // the second process is the printing of the report, this is done in the
0384: // processReport() method.
0385: processingContext.setPrepareRun(true);
0386:
0387: // now process all function levels.
0388: // there is at least one level defined, as we added the PageLayouter
0389: // to the report.
0390: // the levels are defined from +inf to 0
0391: // we don't draw and we do not collect states in a StateList yet
0392: final int[] levels = StateUtilities.computeLevels(report,
0393: lp);
0394: if (levels.length == 0) {
0395: throw new IllegalStateException(
0396: "Assertation Failed: No functions defined, invalid implementation.");
0397: }
0398: processingContext.setProgressLevelCount(levels.length);
0399: int index = 0;
0400: int level = levels[index];
0401: // outer loop: process all function levels
0402: boolean hasNext;
0403: do {
0404: processingContext.setProcessingLevel(level);
0405: processingContext.setProgressLevel(index);
0406:
0407: // if the current level is the output-level, then save the report state.
0408: // The state is used later to restart the report processing.
0409: if (level == LayoutProcess.LEVEL_PAGINATE) {
0410: stateList = new PageStateList(this );
0411: physicalMapping = new IntList(40);
0412: logicalMapping = new IntList(20);
0413: Log.debug("Pagination started ..");
0414: state = processPaginationLevel(state, stateList,
0415: maxRows);
0416: } else {
0417: state = processPrepareLevels(state, level, maxRows);
0418: }
0419:
0420: // if there is an other level to process, then use the finish state to
0421: // create a new start state, which will continue the report processing on
0422: // the next higher level.
0423: hasNext = (index < (levels.length - 1));
0424: if (hasNext) {
0425: index += 1;
0426: level = levels[index];
0427: processingContext.setProcessingLevel(level);
0428: processingContext.setProgressLevel(index);
0429: if (state.isFinish()) {
0430: state = state.restart();
0431: // this is a paranoid check ...
0432: if (state.getCurrentPage() != ReportState.BEFORE_FIRST_PAGE) {
0433: throw new IllegalStateException(
0434: "State was not set up properly");
0435: }
0436: } else {
0437: throw new IllegalStateException(
0438: "Repaginate did not produce an finish state");
0439: }
0440: }
0441: } while (hasNext == true);
0442:
0443: // finally return the saved page states.
0444: processingContext.setPrepareRun(false);
0445: } catch (ReportDataFactoryException e) {
0446: throw new ReportProcessingException(
0447: "Unable to initialize the report", e);
0448: }
0449:
0450: final long end = System.currentTimeMillis();
0451: Log.debug("Pagination-Time: " + (end - start));
0452:
0453: }
0454:
0455: /**
0456: * Processes the print level for the current report. This function will fill the report state list while performing
0457: * the repagination.
0458: *
0459: * @param startState the start state for the print level.
0460: * @param pageStates the list of report states that should receive the created page states.
0461: * @param maxRows the number of rows in the report (used to estaminate the current progress).
0462: * @return the finish state for the report.
0463: * @throws ReportProcessingException if there was a problem processing the report.
0464: */
0465: private ProcessState processPaginationLevel(
0466: final ProcessState startState,
0467: final PageStateList pageStates, final int maxRows)
0468: throws ReportProcessingException {
0469: try {
0470: final boolean failOnError = ReportConfigurationUtil
0471: .isStrictErrorHandling(getReport()
0472: .getReportConfiguration());
0473: final ReportProcessingErrorHandler errorHandler = new CollectingReportErrorHandler();
0474: final DefaultLayoutPagebreakHandler pagebreakHandler = new DefaultLayoutPagebreakHandler();
0475:
0476: final ProcessState initialReportState = startState
0477: .deriveForStorage();
0478: final PageState initialPageState = new PageState(
0479: initialReportState, outputProcessor.getPageCursor());
0480: pageStates.add(initialPageState);
0481:
0482: final ReportProgressEvent repaginationState = new ReportProgressEvent(
0483: this );
0484:
0485: // inner loop: process the complete report, calculate the function values
0486: // for the current level. Higher level functions are not available in the
0487: // dataRow.
0488: final int eventTrigger = Math.min(maxRows
0489: / MAX_EVENTS_PER_RUN, MIN_ROWS_PER_EVENT);
0490:
0491: ProcessState state = startState.deriveForStorage();
0492: state.setErrorHandler(errorHandler);
0493: validate(state);
0494:
0495: int pageEventCount = 0;
0496: // First and last derive of a page must be a storage derivate - this clones everything and does
0497: // not rely on the more complicated transactional layouting ..
0498: ProcessState fallBackState = startState
0499: .deriveForPagebreak();
0500: // ProcessState debugState = startState.deriveForStorage();
0501: // ProcessState lastCommitedState = null;
0502:
0503: Object rollbackPageState = null;
0504: int rollBackCount = 0;
0505: boolean isInRollBackMode = false;
0506:
0507: int eventCount = 0;
0508: int lastRow = -1;
0509: while (!state.isFinish()) {
0510: int logPageCount = outputProcessor
0511: .getLogicalPageCount();
0512: int physPageCount = outputProcessor
0513: .getPhysicalPageCount();
0514:
0515: checkInterrupted();
0516: if (lastRow != state.getCurrentDataItem()) {
0517: lastRow = state.getCurrentDataItem();
0518: if (eventCount == 0) {
0519: if (isPagebreaksSupported()
0520: && fallBackState != null) {
0521: repaginationState.reuse(
0522: ReportProgressEvent.PAGINATING,
0523: fallBackState.getCurrentDataItem(),
0524: fallBackState.getNumberOfRows(),
0525: fallBackState.getCurrentPage(),
0526: fallBackState.getProgressLevel(),
0527: fallBackState
0528: .getProgressLevelCount());
0529: } else {
0530: repaginationState.reuse(
0531: ReportProgressEvent.PAGINATING,
0532: state.getCurrentDataItem(), state
0533: .getNumberOfRows(), state
0534: .getCurrentPage(), state
0535: .getProgressLevel(), state
0536: .getProgressLevelCount());
0537: }
0538: fireStateUpdate(repaginationState);
0539: eventCount += 1;
0540: } else {
0541: if (eventCount == eventTrigger) {
0542: eventCount = 0;
0543: } else {
0544: eventCount += 1;
0545: }
0546: }
0547: }
0548:
0549: final ProcessState restoreState = fallBackState;
0550: if (isPagebreaksSupported()) {
0551: if (isInRollBackMode == false) {
0552: if (pageEventCount == PAGE_EVENT_RATE) {
0553:
0554: // lastCommitedState = fallBackState.deriveForStorage();
0555: //
0556: final DefaultOutputFunction commitableOutputFunction = (DefaultOutputFunction) state
0557: .getLayoutProcess()
0558: .getOutputFunction();
0559: final Renderer commitableRenderer = commitableOutputFunction
0560: .getRenderer();
0561: commitableRenderer
0562: .createRollbackInformation();
0563:
0564: // Log.debug ("Deriving " + fallBackState.getProcessKey());
0565: fallBackState = state.deriveForPagebreak();
0566: // debugState = state.deriveForStorage();
0567: validate(state);
0568: pageEventCount = 0;
0569: } else {
0570: pageEventCount += 1;
0571: }
0572: } else {
0573: // on rollback, increase the count, but never create new fallback states.
0574: rollBackCount += 1;
0575: }
0576: }
0577:
0578: final ProcessState nextState = state.advance();
0579: state
0580: .setErrorHandler(IgnoreEverythingReportErrorHandler.INSTANCE);
0581: state = nextState;
0582: validate(state);
0583:
0584: final ReportStateKey nextStateKey = state
0585: .getProcessKey();
0586:
0587: if (errorHandler.isErrorOccured() == true) {
0588: final List childExceptions = Arrays
0589: .asList(errorHandler.getErrors());
0590: errorHandler.clearErrors();
0591: if (failOnError) {
0592: throw new ReportEventException(
0593: "Failed to dispatch an event.",
0594: childExceptions);
0595: } else {
0596: final ReportEventException exception = new ReportEventException(
0597: "Failed to dispatch an event.",
0598: childExceptions);
0599: Log.error("Failed to dispatch an event.",
0600: exception);
0601: }
0602: }
0603:
0604: final DefaultOutputFunction lm = (DefaultOutputFunction) state
0605: .getLayoutProcess().getOutputFunction();
0606: final Renderer renderer = lm.getRenderer();
0607: pagebreakHandler.setReportState(state);
0608:
0609: if (isInRollBackMode) {
0610: if (rollBackCount > PAGE_EVENT_RATE) {
0611: // we missed the state that caused the pagebreak. This means, that the report behaved
0612: // non-deterministic - which is bad.
0613: //throw new ReportProcessingException ("Failed to rollback during the page processing: " + rollBackCount);
0614: }
0615:
0616: // todo: Could be that we have to use the other key here..
0617: // was: state.getProcessKey()
0618: if (nextStateKey.equals(rollbackPageState)) {
0619: // reached the border case. We have to insert a manual pagebreak here or at least
0620: // we have to force the renderer to end the page right now.
0621: //Log.debug ("HERE: Found real pagebreak position. This might be the last state we process.");
0622: renderer.addPagebreak(state.getProcessKey());
0623: }
0624: }
0625:
0626: final boolean pagebreakEncountered = renderer
0627: .validatePages();
0628: if (pagebreakEncountered) {
0629: final Object lastVisibleStateKey = renderer
0630: .getLastStateKey();
0631: if (isPagebreaksSupported()
0632: && isInRollBackMode == false
0633: && lastVisibleStateKey != null
0634: && renderer.isOpen()) {
0635: if (lastVisibleStateKey.equals(nextStateKey) == false) {
0636: // Roll back to the last known to be good position and process the states up to, but not
0637: // including the current state. This way, we can fire the page-events *before* this band
0638: // gets printed.
0639: rollbackPageState = lastVisibleStateKey;
0640: rollBackCount = 0;
0641: state = restoreState.deriveForPagebreak();
0642:
0643: final DefaultOutputFunction rollbackOutputFunction = (DefaultOutputFunction) state
0644: .getLayoutProcess()
0645: .getOutputFunction();
0646: final Renderer rollbackRenderer = rollbackOutputFunction
0647: .getRenderer();
0648: rollbackRenderer.rollback();
0649: if (SHOW_ROLLBACKS) {
0650: Log
0651: .debug("HERE: Encountered bad break, need to roll-back: "
0652: + rollbackPageState);
0653: Log
0654: .debug("HERE: : "
0655: + state.getProcessKey());
0656: Log
0657: .debug("HERE: : "
0658: + restoreState
0659: .getProcessKey());
0660: }
0661:
0662: validate(state);
0663:
0664: isInRollBackMode = true;
0665: fallBackState = null; // there is no way we can fall-back inside a roll-back ..
0666: continue;
0667: } else {
0668: // The current state printed content partially on the now finished page and there is more
0669: // content on the currently open page. This is a in-between pagebreak, we invoke a pagebreak
0670: // after this state has been processed.
0671: if (SHOW_ROLLBACKS) {
0672: Log
0673: .debug("HERE: Encountered on-going break "
0674: + lastVisibleStateKey);
0675: }
0676: }
0677: } else {
0678: if (SHOW_ROLLBACKS) {
0679: Log
0680: .debug("HERE: Encountered a good break or a roll-back break: "
0681: + isInRollBackMode);
0682: Log
0683: .debug("HERE: : "
0684: + state.getProcessKey());
0685: }
0686: isInRollBackMode = false;
0687: rollbackPageState = null;
0688: rollBackCount = 0;
0689: }
0690:
0691: if (isPagebreaksSupported() == false) {
0692: // The commit causes all closed-nodes to become finishable. This allows the process-page
0693: // and the incremental-update methods to remove the nodes. For non-streaming targets (where
0694: // pagebreaks are possible) the commit state is managed manually
0695: renderer.applyAutoCommit();
0696: }
0697:
0698: if (renderer.processPage(pagebreakHandler, state
0699: .getProcessKey(), true) == false) {
0700: throw new IllegalStateException(
0701: "This cannot be. If the validation said we get a new page, how can we now get lost here");
0702: }
0703:
0704: state = state.commit();
0705:
0706: // can continue safely ..
0707: final int newLogPageCount = outputProcessor
0708: .getLogicalPageCount();
0709: final int newPhysPageCount = outputProcessor
0710: .getPhysicalPageCount();
0711:
0712: final int result = stateList.size() - 1;
0713: for (; physPageCount < newPhysPageCount; physPageCount++) {
0714: physicalMapping.add(result);
0715: }
0716:
0717: for (; logPageCount < newLogPageCount; logPageCount++) {
0718: logicalMapping.add(result);
0719: }
0720:
0721: if (state.isFinish() == false) {
0722: // A pagebreak has occured ...
0723: // We add all but the last state ..
0724: final PageState pageState = new PageState(state
0725: .deriveForStorage(), outputProcessor
0726: .getPageCursor());
0727: stateList.add(pageState);
0728: }
0729:
0730: if (isPagebreaksSupported()) {
0731: fallBackState = state.deriveForPagebreak();
0732: pageEventCount = 0;
0733: eventCount = 0;
0734: }
0735: } else {
0736: if (isPagebreaksSupported() == false) {
0737: renderer.applyAutoCommit();
0738: }
0739:
0740: // PageEventCount is zero on streaming exports and zero after a new rollback event is created.
0741: if (pageEventCount == 0
0742: && isInRollBackMode == false) {
0743: renderer.processIncrementalUpdate(true);
0744: }
0745: state = state.commit();
0746:
0747: // Expected a pagebreak now, but did not encounter one.
0748: // todo: Could be that we have to use the other key here ..
0749: // if (nextStateKey.equals(rollbackPageState))
0750: // {
0751: // // reached the border case. We have to insert a manual pagebreak here or at least
0752: // // we have to force the renderer to end the page right now.
0753: // // todo
0754: // Log.debug("HERE: Ups, Found real pagebreak position. but where is my break?");
0755: // renderer.addPagebreak(state.getProcessKey());
0756: // }
0757:
0758: if (fallBackState != restoreState) {
0759: final DefaultOutputFunction commitableOutputFunction = (DefaultOutputFunction) state
0760: .getLayoutProcess().getOutputFunction();
0761: final Renderer commitableRenderer = commitableOutputFunction
0762: .getRenderer();
0763: commitableRenderer.applyRollbackInformation();
0764: }
0765: }
0766:
0767: }
0768: return initialReportState;
0769: } catch (ContentProcessingException e) {
0770: throw new ReportProcessingException(
0771: "Content-Processing failed.", e);
0772: }
0773: }
0774:
0775: private void validate(final ProcessState state) {
0776: if (paranoidChecks) {
0777: final DefaultOutputFunction of = (DefaultOutputFunction) state
0778: .getLayoutProcess().getOutputFunction();
0779: final AbstractRenderer r = (AbstractRenderer) of
0780: .getRenderer();
0781: r.performParanoidModelCheck();
0782: }
0783: }
0784:
0785: public boolean isPaginated() {
0786: return stateList != null;
0787: }
0788:
0789: protected PageState getLogicalPageState(final int page) {
0790: final int index = logicalMapping.get(page);
0791: final PageState pageState = stateList.get(index);
0792: if (pageState == null) {
0793: throw new IndexOutOfBoundsException(
0794: "The logical mapping between page " + page
0795: + " and index " + index + " is invalid.");
0796: }
0797: return pageState;
0798: }
0799:
0800: protected PageState getPhysicalPageState(final int page) {
0801: final int index = physicalMapping.get(page);
0802: final PageState pageState = stateList.get(index);
0803: if (pageState == null) {
0804: throw new IndexOutOfBoundsException(
0805: "The physical mapping between page " + page
0806: + " and index " + index + " is invalid.");
0807: }
0808: return pageState;
0809: }
0810:
0811: public PageState processPage(final PageState pageState,
0812: final boolean performOutput)
0813: throws ReportProcessingException {
0814: if (pageState == null) {
0815: throw new NullPointerException(
0816: "PageState must not be null.");
0817: }
0818: final boolean failOnError = ReportConfigurationUtil
0819: .isStrictErrorHandling(getReport()
0820: .getReportConfiguration());
0821: final ReportProcessingErrorHandler errorHandler = new CollectingReportErrorHandler();
0822: //todo
0823:
0824: try {
0825: final ProcessState startState = pageState.getReportState();
0826: outputProcessor.setPageCursor(pageState.getPageCursor());
0827: final int maxRows = startState.getNumberOfRows();
0828: final ReportProgressEvent repaginationState = new ReportProgressEvent(
0829: this );
0830: final DefaultLayoutPagebreakHandler pagebreakHandler = new DefaultLayoutPagebreakHandler();
0831: // inner loop: process the complete report, calculate the function values
0832: // for the current level. Higher level functions are not available in the
0833: // dataRow.
0834: final int eventTrigger = Math.min(maxRows
0835: / MAX_EVENTS_PER_RUN, MIN_ROWS_PER_EVENT);
0836:
0837: Object rollbackPageState = null;
0838:
0839: ProcessState state = startState.deriveForStorage();
0840: ProcessState fallBackState = state.deriveForPagebreak();
0841: state.setErrorHandler(errorHandler);
0842:
0843: int rollBackCount = 0;
0844: boolean isInRollBackMode = false;
0845: int lastRow = -1;
0846: int eventCount = 0;
0847: int pageEventCount = 0;
0848: while (!state.isFinish()) {
0849: checkInterrupted();
0850: if (lastRow != state.getCurrentDataItem()) {
0851: lastRow = state.getCurrentDataItem();
0852: if (eventCount == 0) {
0853: repaginationState.reuse(
0854: ReportProgressEvent.GENERATING_CONTENT,
0855: state.getCurrentDataItem(), maxRows,
0856: state.getCurrentPage(), state
0857: .getProgressLevel(), state
0858: .getProgressLevelCount());
0859: fireStateUpdate(repaginationState);
0860: eventCount += 1;
0861: } else {
0862: if (eventCount == eventTrigger) {
0863: eventCount = 0;
0864: } else {
0865: eventCount += 1;
0866: }
0867: }
0868: }
0869:
0870: final ProcessState restoreState = fallBackState;
0871: if (isPagebreaksSupported()) {
0872: if (isInRollBackMode == false) {
0873: if (pageEventCount == PAGE_EVENT_RATE) {
0874: final DefaultOutputFunction commitableOutputFunction = (DefaultOutputFunction) state
0875: .getLayoutProcess()
0876: .getOutputFunction();
0877: final Renderer commitableRenderer = commitableOutputFunction
0878: .getRenderer();
0879: commitableRenderer
0880: .createRollbackInformation();
0881:
0882: fallBackState = state.deriveForPagebreak();
0883: pageEventCount = 0;
0884: } else {
0885: pageEventCount += 1;
0886: }
0887: } else {
0888: // on rollback, increase the count, but never create new fallback states.
0889: rollBackCount += 1;
0890: }
0891: }
0892:
0893: final ProcessState nextState = state.advance();
0894: state
0895: .setErrorHandler(IgnoreEverythingReportErrorHandler.INSTANCE);
0896: state = nextState;
0897:
0898: final ReportStateKey nextStateKey = state
0899: .getProcessKey();
0900:
0901: if (errorHandler.isErrorOccured() == true) {
0902: final List childExceptions = Arrays
0903: .asList(errorHandler.getErrors());
0904: errorHandler.clearErrors();
0905: if (failOnError) {
0906: throw new ReportEventException(
0907: "Failed to dispatch an event.",
0908: childExceptions);
0909: } else {
0910: final ReportEventException exception = new ReportEventException(
0911: "Failed to dispatch an event.",
0912: childExceptions);
0913: Log.error("Failed to dispatch an event.",
0914: exception);
0915: }
0916: }
0917:
0918: final DefaultOutputFunction lm = (DefaultOutputFunction) state
0919: .getLayoutProcess().getOutputFunction();
0920: final Renderer renderer = lm.getRenderer();
0921: pagebreakHandler.setReportState(state);
0922:
0923: if (isInRollBackMode) {
0924: if (rollBackCount > PAGE_EVENT_RATE) {
0925: // we missed the state that caused the pagebreak. This means, that the report behaved
0926: // non-deterministic - which is bad.
0927: //throw new ReportProcessingException ("Failed to rollback during the page processing: " + rollBackCount);
0928: }
0929:
0930: if (nextStateKey.equals(rollbackPageState)) {
0931: // reached the border case. We have to insert a manual pagebreak here or at least
0932: // we have to force the renderer to end the page right now.
0933: // Log.debug ("HERE: Found real pagebreak position. This might be the last state we process.");
0934: renderer.addPagebreak(state.getProcessKey());
0935: }
0936: }
0937:
0938: final boolean pagebreakEncountered = (renderer
0939: .validatePages());
0940: if (pagebreakEncountered) {
0941: final Object lastStateKey = renderer
0942: .getLastStateKey();
0943: if (isPagebreaksSupported()
0944: && isInRollBackMode == false
0945: && renderer.isOpen()
0946: && lastStateKey != null) {
0947: if (lastStateKey.equals(nextStateKey) == false) {
0948: // Log.debug ("HERE: Encountered bad break, need to roll-back");
0949:
0950: // Roll back to the last known to be good position and process the states up to, but not
0951: // including the current state. This way, we can fire the page-events *before* this band
0952: // gets printed.
0953: rollbackPageState = lastStateKey;
0954: rollBackCount = 0;
0955: state = restoreState.deriveForPagebreak();
0956:
0957: final DefaultOutputFunction rollbackOutputFunction = (DefaultOutputFunction) state
0958: .getLayoutProcess()
0959: .getOutputFunction();
0960: final Renderer rollbackRenderer = rollbackOutputFunction
0961: .getRenderer();
0962: rollbackRenderer.rollback();
0963: if (SHOW_ROLLBACKS) {
0964: Log
0965: .debug("HERE: Encountered bad break, need to roll-back: "
0966: + rollbackPageState);
0967: Log
0968: .debug("HERE: : "
0969: + state.getProcessKey());
0970: Log
0971: .debug("HERE: : "
0972: + restoreState
0973: .getProcessKey());
0974: }
0975:
0976: validate(state);
0977:
0978: isInRollBackMode = true;
0979: continue;
0980: }
0981: // else
0982: // {
0983: // // The current state printed content partially on the now finished page and there is more
0984: // // content on the currently open page. This is a in-between pagebreak, we invoke a pagebreak
0985: // // after this state has been processed.
0986: // Log.debug ("HERE: Encountered on-going break " + lastStateKey);
0987: // }
0988: }
0989: // else
0990: // {
0991: // Log.debug ("HERE: Encountered a good break or a roll-back break: " + isInRollBackMode);
0992: // Log.debug ("HERE: : " + state.getProcessKey());
0993: // }
0994: if (isPagebreaksSupported() == false) {
0995: // The commit causes all closed-nodes to become finishable. This allows the process-page
0996: // and the incremental-update methods to remove the nodes. For non-streaming targets (where
0997: // pagebreaks are possible) the commit state is managed manually
0998: renderer.applyAutoCommit();
0999: }
1000:
1001: if (renderer.processPage(pagebreakHandler, state
1002: .getProcessKey(), performOutput) == false) {
1003: throw new IllegalStateException(
1004: "This must not be.");
1005: }
1006:
1007: state = state.commit();
1008: if (renderer.isOpen()) {
1009: // No need to create a copy here. It is part of the contract that the resulting page state must be
1010: // cloned before it can be used again. The only place where it is used is this method, so we can
1011: // be pretty sure that this contract is valid.
1012: return new PageState(state, outputProcessor
1013: .getPageCursor());
1014: }
1015: } else {
1016: if (isPagebreaksSupported() == false) {
1017: // The commit causes all closed-nodes to become finishable. This allows the process-page
1018: // and the incremental-update methods to remove the nodes. For non-streaming targets (where
1019: // pagebreaks are possible) the commit state is managed manually
1020: renderer.applyAutoCommit();
1021: }
1022:
1023: if (pageEventCount == 0
1024: && isInRollBackMode == false) {
1025: renderer
1026: .processIncrementalUpdate(performOutput);
1027: }
1028: state = state.commit();
1029:
1030: // Expected a pagebreak now, but did not encounter one.
1031: // if (nextStateKey.equals(rollbackPageState))
1032: // {
1033: // // reached the border case. We have to insert a manual pagebreak here or at least
1034: // // we have to force the renderer to end the page right now.
1035: // Log.debug ("HERE: Ups, Found real pagebreak position. but where is my break?");
1036: // //renderer.addPagebreak(state.getProcessKey());
1037: // }
1038: if (fallBackState != restoreState) {
1039: final DefaultOutputFunction commitableOutputFunction = (DefaultOutputFunction) state
1040: .getLayoutProcess().getOutputFunction();
1041: final Renderer commitableRenderer = commitableOutputFunction
1042: .getRenderer();
1043: commitableRenderer.applyRollbackInformation();
1044: }
1045: }
1046: }
1047:
1048: // We should never reach this point, if this function has been called by the PageStateList.
1049: return null;
1050: }
1051: // catch (CloneNotSupportedException e)
1052: // {
1053: // throw new ReportProcessingException("Clone failed. This cannot be.");
1054: // }
1055: catch (ContentProcessingException e) {
1056: throw new ReportProcessingException(
1057: "Content-Processing failed.", e);
1058: }
1059: }
1060:
1061: public void processReport() throws ReportProcessingException {
1062: if (Log.isDebugEnabled()) {
1063: Log.debug(new MemoryUsageMessage(System
1064: .identityHashCode(Thread.currentThread())
1065: + ": Report processing time: Starting: "));
1066: }
1067: try {
1068: final long startTime = System.currentTimeMillis();
1069: if (isPaginated() == false) {
1070: // Processes the whole report ..
1071: prepareReportProcessing();
1072: }
1073:
1074: final long paginateTime = System.currentTimeMillis();
1075: if (Log.isDebugEnabled()) {
1076: Log.debug(new MemoryUsageMessage(System
1077: .identityHashCode(Thread.currentThread())
1078: + ": Report processing time: Pagination time: "
1079: + ((paginateTime - startTime) / 1000.0)));
1080: }
1081: if (getLogicalPageCount() == 0) {
1082: throw new EmptyReportException(
1083: "Report did not generate any content.");
1084: }
1085:
1086: // Start from scratch ...
1087: PageState state = getLogicalPageState(0);
1088: while (state != null) {
1089: state = processPage(state, true);
1090: }
1091: final long endTime = System.currentTimeMillis();
1092: if (Log.isDebugEnabled()) {
1093: Log.debug(new MemoryUsageMessage(System
1094: .identityHashCode(Thread.currentThread())
1095: + ": Report processing time: "
1096: + ((endTime - startTime) / 1000.0)));
1097: }
1098: } catch (EmptyReportException re) {
1099: throw re;
1100: } catch (ReportProcessingException re) {
1101: Log.error(System.identityHashCode(Thread.currentThread())
1102: + ": Report processing failed.");
1103: throw re;
1104: } catch (Exception e) {
1105: Log.error(System.identityHashCode(Thread.currentThread())
1106: + ": Report processing failed.");
1107: throw new ReportProcessingException(
1108: "Failed to process the report", e);
1109: }
1110: if (Log.isDebugEnabled()) {
1111: Log.debug(System.identityHashCode(Thread.currentThread())
1112: + ": Report processing finished.");
1113: }
1114: }
1115:
1116: public int getLogicalPageCount() {
1117: return logicalMapping.size();
1118: }
1119:
1120: /**
1121: * Checks, whether the output mode may generate pagebreaks. If we have to deal with pagebreaks, we may have to perform
1122: * roll-backs and commits to keep the pagebreaks in sync with the state-processing. This is ugly, expensive and you
1123: * better dont try this at home.
1124: * <p/>
1125: * The roll-back is done for paginated and flow-report outputs, but if we have no autmoatic and manual pagebreaks,
1126: * there is no need to even consider to roll-back to a state before the pagebreak (which will never occur).
1127: *
1128: * @return a flag indicating whether the output target supports pagebreaks.
1129: */
1130: protected boolean isPagebreaksSupported() {
1131: return pagebreaksSupported;
1132: }
1133: }
|