Package org.jberet.runtime.runner

Source Code of org.jberet.runtime.runner.ChunkRunner

/*
* Copyright (c) 2012-2014 Red Hat, Inc. and/or its affiliates.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Cheng Fang - Initial API and implementation
*/

package org.jberet.runtime.runner;

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.batch.api.chunk.CheckpointAlgorithm;
import javax.batch.api.chunk.ItemProcessor;
import javax.batch.api.chunk.ItemReader;
import javax.batch.api.chunk.ItemWriter;
import javax.batch.api.chunk.listener.ChunkListener;
import javax.batch.api.chunk.listener.ItemProcessListener;
import javax.batch.api.chunk.listener.ItemReadListener;
import javax.batch.api.chunk.listener.ItemWriteListener;
import javax.batch.api.chunk.listener.RetryProcessListener;
import javax.batch.api.chunk.listener.RetryReadListener;
import javax.batch.api.chunk.listener.RetryWriteListener;
import javax.batch.api.chunk.listener.SkipProcessListener;
import javax.batch.api.chunk.listener.SkipReadListener;
import javax.batch.api.chunk.listener.SkipWriteListener;
import javax.batch.api.partition.PartitionCollector;
import javax.batch.runtime.BatchStatus;
import javax.batch.runtime.Metric;
import javax.transaction.Status;
import javax.transaction.TransactionManager;

import org.jberet.job.model.Chunk;
import org.jberet.job.model.ExceptionClassFilter;
import org.jberet.job.model.Listeners;
import org.jberet.job.model.Properties;
import org.jberet.job.model.RefArtifact;
import org.jberet.runtime.AbstractStepExecution;
import org.jberet.runtime.context.StepContextImpl;
import org.jberet.runtime.metric.StepMetrics;

import static org.jberet._private.BatchLogger.LOGGER;
import static org.jberet._private.BatchMessages.MESSAGES;

/**
* This runner class is responsible for running a chunk-type step (not just a chunk range of a step).  In a partitioned
* step execution, multiple ChunkRunner instances are created, one for each partition.  The StepContextImpl and
* StepExecutionImpl associated with each ChunkRunner in a partition are cloned from the original counterparts.
*
* @author <a href="mailto:jperkins@redhat.com">James R. Perkins</a>
*/
public final class ChunkRunner extends AbstractRunner<StepContextImpl> implements Runnable {
    private final List<Object> allChunkRelatedListeners = new ArrayList<Object>();
    private final List<ChunkListener> chunkListeners = new ArrayList<ChunkListener>();

    private final List<SkipWriteListener> skipWriteListeners = new ArrayList<SkipWriteListener>();
    private final List<SkipProcessListener> skipProcessListeners = new ArrayList<SkipProcessListener>();
    private final List<SkipReadListener> skipReadListeners = new ArrayList<SkipReadListener>();

    private final List<RetryReadListener> retryReadListeners = new ArrayList<RetryReadListener>();
    private final List<RetryWriteListener> retryWriteListeners = new ArrayList<RetryWriteListener>();
    private final List<RetryProcessListener> retryProcessListeners = new ArrayList<RetryProcessListener>();

    private final List<ItemReadListener> itemReadListeners = new ArrayList<ItemReadListener>();
    private final List<ItemWriteListener> itemWriteListeners = new ArrayList<ItemWriteListener>();
    private final List<ItemProcessListener> itemProcessListeners = new ArrayList<ItemProcessListener>();

    private final Chunk chunk;
    private final StepExecutionRunner stepRunner;
    private final StepMetrics stepMetrics;
    private AbstractStepExecution stepOrPartitionExecution;
    private final ItemReader itemReader;
    private final ItemWriter itemWriter;
    private ItemProcessor itemProcessor;
    private PartitionCollector collector;

    private String checkpointPolicy = "item";
    private CheckpointAlgorithm checkpointAlgorithm;
    private int itemCount = 10;
    private int timeLimit;  //in seconds
    private int skipLimit;  //default no limit
    private int retryLimit;  //default no limit

    private final ExceptionClassFilter skippableExceptionClasses;
    private final ExceptionClassFilter retryableExceptionClasses;
    private final ExceptionClassFilter noRollbackExceptionClasses;
    private int skipCount;
    private int retryCount;

    private Object itemRead;
    private final List<Object> outputList = new ArrayList<Object>();

    private final TransactionManager tm;

    public ChunkRunner(final StepContextImpl stepContext,
                       final CompositeExecutionRunner enclosingRunner,
                       final StepExecutionRunner stepRunner,
                       final Chunk chunk) throws Exception {
        super(stepContext, enclosingRunner);
        this.stepRunner = stepRunner;
        this.chunk = chunk;
        this.stepOrPartitionExecution = stepContext.getStepExecution();
        this.stepMetrics = this.stepOrPartitionExecution.getStepMetrics();

        itemReader = (ItemReader) createArtifact(chunk.getReader(), batchContext, ScriptItemReader.class);
        itemWriter = (ItemWriter) createArtifact(chunk.getWriter(), batchContext, ScriptItemWriter.class);

        final RefArtifact processorElement = chunk.getProcessor();
        if (processorElement != null) {
            itemProcessor = (ItemProcessor) createArtifact(processorElement, batchContext, ScriptItemProcessor.class);
        }

        if (stepRunner.collectorDataQueue != null) {
            final RefArtifact collectorConfig = batchContext.getStep().getPartition().getCollector();
            if (collectorConfig != null) {
                collector = jobContext.createArtifact(collectorConfig.getRef(), null, collectorConfig.getProperties(), batchContext);
            }
        }

        String attrVal = chunk.getCheckpointPolicy();
        if (attrVal == null || attrVal.equals("item")) {
            attrVal = chunk.getItemCount();
            if (attrVal != null) {
                itemCount = Integer.parseInt(attrVal);
                if (itemCount < 1) {
                    throw MESSAGES.invalidItemCount(itemCount);
                }
            }
            attrVal = chunk.getTimeLimit();
            if (attrVal != null) {
                timeLimit = Integer.parseInt(attrVal);
            }
        } else if (attrVal.equals("custom")) {
            checkpointPolicy = "custom";
            final RefArtifact alg = chunk.getCheckpointAlgorithm();
            if (alg != null) {
                checkpointAlgorithm = jobContext.createArtifact(alg.getRef(), null, alg.getProperties(), batchContext);
            } else {
                throw MESSAGES.checkpointAlgorithmMissing(stepRunner.step.getId());
            }
        } else {
            throw MESSAGES.invalidCheckpointPolicy(attrVal);
        }

        attrVal = chunk.getSkipLimit();
        skipLimit = attrVal == null ? -1 : Integer.parseInt(attrVal);

        attrVal = chunk.getRetryLimit();
        retryLimit = attrVal == null ? -1 : Integer.parseInt(attrVal);

        skippableExceptionClasses = chunk.getSkippableExceptionClasses();
        retryableExceptionClasses = chunk.getRetryableExceptionClasses();
        noRollbackExceptionClasses = chunk.getNoRollbackExceptionClasses();
        this.tm = stepRunner.tm;
        createChunkRelatedListeners();
    }

    @Override
    public void run() {
        try {
            //When running in EE environment, set global transaction timeout for the current thread
            // from javax.transaction.global.timeout property at step level
            final Properties stepProps = stepRunner.step.getProperties();
            int globalTimeout = 180; //default 180 seconds defined by spec
            if (stepProps != null) {
                final String globalTimeoutProp = stepProps.get("javax.transaction.global.timeout");
                if (globalTimeoutProp != null) {
                    globalTimeout = Integer.valueOf(globalTimeoutProp);
                }
            }
            tm.setTransactionTimeout(globalTimeout);
            tm.begin();
            try {
                itemReader.open(stepOrPartitionExecution.getReaderCheckpointInfo());
                itemWriter.open(stepOrPartitionExecution.getWriterCheckpointInfo());
                tm.commit();
            } catch (Exception e) {
                tm.rollback();
                // An error occurred, safely close the reader and writer
                safeClose();
                throw e;
            }

            readProcessWriteItems();

            tm.begin();
            try {
                itemWriter.close();
                itemReader.close();
                tm.commit();
            } catch (Exception e) {
                tm.rollback();
                // An error occurred, safely close the reader and writer
                safeClose();
                throw e;
            }
            //collect data at the end of the partition
            if (collector != null) {
                stepRunner.collectorDataQueue.put(collector.collectPartitionData());
            }
            //set batch status to indicate that either the main step, or a partition has completed successfully.
            //note that when a chunk range is completed, we should not set batch status as completed.
            //make sure the step has not been set to STOPPED.
            if (batchContext.getBatchStatus() == BatchStatus.STARTED) {
                batchContext.setBatchStatus(BatchStatus.COMPLETED);
            }
        } catch (Exception e) {
            batchContext.setException(e);
            LOGGER.failToRunJob(e, jobContext.getJobName(), batchContext.getStepName(), chunk);
            batchContext.setBatchStatus(BatchStatus.FAILED);
        } finally {
            try {
                if (stepRunner.collectorDataQueue != null) {
                    stepRunner.collectorDataQueue.put(stepOrPartitionExecution);
                }
            } catch (InterruptedException e) {
                //ignore
            }
            if (stepRunner.completedPartitionThreads != null) {
                stepRunner.completedPartitionThreads.offer(Boolean.TRUE);
            }
            jobContext.destroyArtifact(itemReader, itemWriter, itemProcessor, collector, checkpointAlgorithm);
            jobContext.destroyArtifact(allChunkRelatedListeners);
            // Safely close the reader and writer
            safeClose();
        }
    }

    /**
     * The main read-process-write loop
     *
     * @throws Exception
     */
    private void readProcessWriteItems() throws Exception {
        final ProcessingInfo processingInfo = new ProcessingInfo();
        //if input has not been depleted, or even if depleted, but still need to retry the last item
        //if stopped, exit the loop
        while ((processingInfo.chunkState != ChunkState.JOB_STOPPED) &&

                (processingInfo.chunkState != ChunkState.DEPLETED ||
                        processingInfo.itemState == ItemState.TO_RETRY_READ ||
                        processingInfo.itemState == ItemState.TO_RETRY_PROCESS ||
                        processingInfo.itemState == ItemState.TO_RETRY_WRITE)
                ) {
            try {
                //reset state for the next iteration
                switch (processingInfo.itemState) {
                    case TO_SKIP:
                        processingInfo.itemState = ItemState.RUNNING;
                        break;
                    case TO_RETRY_READ:
                        processingInfo.itemState = ItemState.RETRYING_READ;
                        break;
                    case TO_RETRY_PROCESS:
                        processingInfo.itemState = ItemState.RETRYING_PROCESS;
                        break;
                    case TO_RETRY_WRITE:
                        processingInfo.itemState = ItemState.RETRYING_WRITE;
                        break;
                }

                if (processingInfo.chunkState == ChunkState.TO_START_NEW ||
                        processingInfo.chunkState == ChunkState.TO_RETRY ||
                        processingInfo.chunkState == ChunkState.RETRYING ||
                        processingInfo.chunkState == ChunkState.TO_END_RETRY) {
                    if (processingInfo.chunkState == ChunkState.TO_START_NEW || processingInfo.chunkState == ChunkState.TO_END_RETRY) {
                        processingInfo.reset();
                    }
                    //if during Chunk RETRYING, and an item is skipped, the ut is still active so no need to begin a new one
                    if (tm.getStatus() != Status.STATUS_ACTIVE) {
                        if (checkpointAlgorithm != null) {
                            tm.setTransactionTimeout(checkpointAlgorithm.checkpointTimeout());
                            checkpointAlgorithm.beginCheckpoint();
                        }
                        tm.begin();
                    }
                    for (final ChunkListener l : chunkListeners) {
                        l.beforeChunk();
                    }
                    beginCheckpoint(processingInfo);
                }

                if (processingInfo.itemState != ItemState.RETRYING_PROCESS && processingInfo.itemState != ItemState.RETRYING_WRITE) {
                    readItem(processingInfo);
                }

                if (itemRead != null && processingInfo.itemState != ItemState.RETRYING_WRITE) {
                    processItem(processingInfo);
                }

                if (processingInfo.toStopItem()) {
                    continue;
                }

                if (isReadyToCheckpoint(processingInfo)) {
                    try {
                        doCheckpoint(processingInfo);

                        //errors may happen during the above doCheckpoint (e.g., in writer.write method).  If so, need
                        //to skip the remainder of the current loop.  If retry with rollback, chunkState has been set to
                        //TO_RETRY; if retry with no rollback, itemState has been set to TO_RETRY_WRITE; if skip,
                        //itemState has been set to TO_SKIP
                        if (processingInfo.chunkState == ChunkState.TO_RETRY || processingInfo.itemState == ItemState.TO_RETRY_WRITE ||
                                processingInfo.itemState == ItemState.TO_SKIP) {
                            continue;
                        }

                        for (final ChunkListener l : chunkListeners) {
                            l.afterChunk();
                        }
                    } catch (final Exception e) {
                        tm.rollback();
                        stepMetrics.increment(Metric.MetricType.ROLLBACK_COUNT, 1);
                        throw e;
                    }

                    tm.commit();
                    if (checkpointAlgorithm != null) {
                        checkpointAlgorithm.endCheckpoint();
                    }

                    stepMetrics.increment(Metric.MetricType.COMMIT_COUNT, 1);
                }
            } catch (final Exception e) {
                final int txStatus = tm.getStatus();
                if (txStatus == Status.STATUS_ACTIVE || txStatus == Status.STATUS_MARKED_ROLLBACK ||
                        txStatus == Status.STATUS_PREPARED || txStatus == Status.STATUS_PREPARING ||
                        txStatus == Status.STATUS_COMMITTING || txStatus == Status.STATUS_ROLLING_BACK) {
                    tm.rollback();
                }
                for (final ChunkListener l : chunkListeners) {
                    l.onError(e);
                }
                throw e;
            }
        }
    }

    private void readItem(final ProcessingInfo processingInfo) throws Exception {
        try {
            for (final ItemReadListener l : itemReadListeners) {
                l.beforeRead();
            }
            itemRead = itemReader.readItem();
            if (itemRead != null) {  //only count successful read
                stepMetrics.increment(Metric.MetricType.READ_COUNT, 1);
                processingInfo.count++;
                processingInfo.readPosition++;
            } else {
                processingInfo.chunkState = ChunkState.DEPLETED;
            }
            for (final ItemReadListener l : itemReadListeners) {
                l.afterRead(itemRead);
            }
        } catch (Exception e) {
            for (final ItemReadListener l : itemReadListeners) {
                l.onReadError(e);
            }
            toSkipOrRetry(e, processingInfo);
            if (processingInfo.itemState == ItemState.TO_SKIP) {
                for (final SkipReadListener l : skipReadListeners) {
                    l.onSkipReadItem(e);
                }
                stepMetrics.increment(Metric.MetricType.READ_SKIP_COUNT, 1);
                skipCount++;
                itemRead = null;
            } else if (processingInfo.itemState == ItemState.TO_RETRY) {
                for (final RetryReadListener l : retryReadListeners) {
                    l.onRetryReadException(e);
                }
                retryCount++;
                if (needRollbackBeforeRetry(e)) {
                    rollbackCheckpoint(processingInfo);
                } else {
                    processingInfo.itemState = ItemState.TO_RETRY_READ;
                }
                itemRead = null;
            } else {
                throw e;
            }
            checkIfEndRetry(processingInfo);
            if (processingInfo.itemState == ItemState.RETRYING_READ) {
                processingInfo.itemState = ItemState.RUNNING;
            }
        }
    }

    private void processItem(final ProcessingInfo processingInfo) throws Exception {
        Object output;
        if (itemProcessor != null) {
            try {
                for (final ItemProcessListener l : itemProcessListeners) {
                    l.beforeProcess(itemRead);
                }
                output = itemProcessor.processItem(itemRead);
                for (final ItemProcessListener l : itemProcessListeners) {
                    l.afterProcess(itemRead, output);
                }
                if (output == null) {
                    stepMetrics.increment(Metric.MetricType.FILTER_COUNT, 1);
                }
            } catch (Exception e) {
                for (final ItemProcessListener l : itemProcessListeners) {
                    l.onProcessError(itemRead, e);
                }
                toSkipOrRetry(e, processingInfo);
                if (processingInfo.itemState == ItemState.TO_SKIP) {
                    for (final SkipProcessListener l : skipProcessListeners) {
                        l.onSkipProcessItem(itemRead, e);
                    }
                    stepMetrics.increment(Metric.MetricType.PROCESS_SKIP_COUNT, 1);
                    skipCount++;
                    output = null;
                } else if (processingInfo.itemState == ItemState.TO_RETRY) {
                    for (final RetryProcessListener l : retryProcessListeners) {
                        l.onRetryProcessException(itemRead, e);
                    }
                    retryCount++;
                    if (needRollbackBeforeRetry(e)) {
                        rollbackCheckpoint(processingInfo);
                    } else {
                        processingInfo.itemState = ItemState.TO_RETRY_PROCESS;
                    }
                    output = null;
                } else {
                    throw e;
                }
            }
        } else {
            output = itemRead;
        }
        //a normal processing can also return null to exclude the processing result from writer.
        if (output != null) {
            outputList.add(output);
        }
        if (processingInfo.itemState != ItemState.TO_RETRY_PROCESS) {
            itemRead = null;
        }
        checkIfEndRetry(processingInfo);
        if (processingInfo.itemState == ItemState.RETRYING_PROCESS) {
            processingInfo.itemState = ItemState.RUNNING;
        }
    }

    private void checkIfEndRetry(final ProcessingInfo processingInfo) {
        if (processingInfo.chunkState == ChunkState.RETRYING &&
                processingInfo.itemState != ItemState.TO_RETRY_READ &&
                processingInfo.itemState != ItemState.TO_RETRY_PROCESS &&
                processingInfo.itemState != ItemState.TO_RETRY_WRITE &&
                processingInfo.readPosition == processingInfo.failurePoint) {
            //if failurePoint is null, should fail with NPE
            processingInfo.chunkState = ChunkState.TO_END_RETRY;
        }
    }

    private void beginCheckpoint(final ProcessingInfo processingInfo) throws Exception {
        if (checkpointPolicy.equals("item")) {
            if (timeLimit > 0) {
                final Timer timer = new Timer("chunk-checkpoint-timer", true);
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        processingInfo.timerExpired = true;
                    }
                }, timeLimit * 1000);
            }
        }
        //if chunk is already RETRYING, do not change it to RUNNING
        if (processingInfo.chunkState == ChunkState.TO_RETRY) {
            processingInfo.chunkState = ChunkState.RETRYING;
        } else if (processingInfo.chunkState != ChunkState.RETRYING) {
            processingInfo.chunkState = ChunkState.RUNNING;
        }
    }

    private boolean isReadyToCheckpoint(final ProcessingInfo processingInfo) throws Exception {
        if (jobContext.getJobExecution().isStopRequested()) {
            processingInfo.chunkState = ChunkState.JOB_STOPPING;
            return true;
        }
        if (processingInfo.chunkState == ChunkState.DEPLETED ||
                processingInfo.chunkState == ChunkState.RETRYING ||
                processingInfo.chunkState == ChunkState.TO_END_RETRY) {
            return true;
        }
        if (checkpointPolicy.equals("item")) {
            if (processingInfo.count >= itemCount) {
                return true;
            }
            if (timeLimit > 0) {
                return processingInfo.timerExpired;
            }
            return false;
        }
        return checkpointAlgorithm.isReadyToCheckpoint();
    }

    private void doCheckpoint(final ProcessingInfo processingInfo) throws Exception {
        final int outputSize = outputList.size();
        if (outputSize == 0 && processingInfo.chunkState == ChunkState.DEPLETED) {
            return;
        }
        try {
            for (final ItemWriteListener l : itemWriteListeners) {
                l.beforeWrite(outputList);
            }
            itemWriter.writeItems(outputList);
            stepMetrics.increment(Metric.MetricType.WRITE_COUNT, outputSize);
            for (final ItemWriteListener l : itemWriteListeners) {
                l.afterWrite(outputList);
            }
            stepOrPartitionExecution.setReaderCheckpointInfo(itemReader.checkpointInfo());
            stepOrPartitionExecution.setWriterCheckpointInfo(itemWriter.checkpointInfo());
            batchContext.savePersistentData();
            outputList.clear();
            processingInfo.checkpointPosition = processingInfo.readPosition;

            if (processingInfo.chunkState == ChunkState.JOB_STOPPING) {
                processingInfo.chunkState = ChunkState.JOB_STOPPED;
                batchContext.setBatchStatus(BatchStatus.STOPPED);
            } else if (processingInfo.chunkState != ChunkState.DEPLETED && processingInfo.chunkState != ChunkState.RETRYING) {
                processingInfo.chunkState = ChunkState.TO_START_NEW;
            }
            if (collector != null) {
                stepRunner.collectorDataQueue.put(collector.collectPartitionData());
            }
        } catch (final Exception e) {
            for (final ItemWriteListener l : itemWriteListeners) {
                l.onWriteError(outputList, e);
            }
            toSkipOrRetry(e, processingInfo);
            if (processingInfo.itemState == ItemState.TO_SKIP) {
                //if requested to stop the job, do not skip to the next item
                if (processingInfo.chunkState == ChunkState.JOB_STOPPING) {
                    processingInfo.chunkState = ChunkState.JOB_STOPPED;
                    batchContext.setBatchStatus(BatchStatus.STOPPED);
                } else if (processingInfo.chunkState != ChunkState.JOB_STOPPED) {
                    for (final SkipWriteListener l : skipWriteListeners) {
                        l.onSkipWriteItem(outputList, e);
                    }
                    stepMetrics.increment(Metric.MetricType.WRITE_SKIP_COUNT, 1);
                    skipCount += outputSize;
                }
            } else if (processingInfo.itemState == ItemState.TO_RETRY) {
                for (final RetryWriteListener l : retryWriteListeners) {
                    l.onRetryWriteException(outputList, e);
                }
                retryCount++;
                if (needRollbackBeforeRetry(e)) {
                    rollbackCheckpoint(processingInfo);
                } else {
                    processingInfo.itemState = ItemState.TO_RETRY_WRITE;
                }
            } else {
                throw e;
            }
        }

        checkIfEndRetry(processingInfo);
        if (processingInfo.itemState == ItemState.RETRYING_WRITE) {
            processingInfo.itemState = ItemState.RUNNING;
        }
    }

    private void rollbackCheckpoint(final ProcessingInfo processingInfo) throws Exception {
        outputList.clear();
        processingInfo.failurePoint = processingInfo.readPosition;
        tm.rollback();
        stepMetrics.increment(Metric.MetricType.ROLLBACK_COUNT, 1);
        // Close the reader and writer
        try {
            itemWriter.close();
            itemReader.close();
        } catch (Exception e) {
            // An error occurred, safely close the reader and writer
            safeClose();
            throw e;
        }
        try {
            // Open the reader and writer
            itemReader.open(stepOrPartitionExecution.getReaderCheckpointInfo());
            processingInfo.readPosition = processingInfo.checkpointPosition;
            itemWriter.open(stepOrPartitionExecution.getWriterCheckpointInfo());
        } catch (Exception e) {
            // An error occurred, safely close the reader and writer
            safeClose();
            throw e;
        }
        processingInfo.chunkState = ChunkState.TO_RETRY;
        processingInfo.itemState = ItemState.RUNNING;
        if (collector != null) {
            stepRunner.collectorDataQueue.put(collector.collectPartitionData());
        }
    }

    private boolean needSkip(final Exception e) {
        return skippableExceptionClasses != null &&
                ((skipLimit >= 0 && skipCount < skipLimit) || skipLimit < 0) &&
                skippableExceptionClasses.matches(e.getClass());
    }

    private boolean needRetry(final Exception e) {
        return retryableExceptionClasses != null &&
                ((retryLimit >= 0 && retryCount < retryLimit) || retryLimit < 0) &&
                retryableExceptionClasses.matches(e.getClass());
    }

    private void toSkipOrRetry(final Exception e, final ProcessingInfo processingInfo) {
        if (processingInfo.chunkState == ChunkState.RETRYING ||
                processingInfo.chunkState == ChunkState.TO_END_RETRY ||
                processingInfo.itemState == ItemState.RETRYING_READ ||
                processingInfo.itemState == ItemState.RETRYING_PROCESS ||
                processingInfo.itemState == ItemState.RETRYING_WRITE) {
            //during retry, skip has precedence over retry
            if (needSkip(e)) {
                processingInfo.itemState = ItemState.TO_SKIP;
                return;
            } else if (needRetry(e)) {
                processingInfo.itemState = ItemState.TO_RETRY;
                return;
            }
        } else {
            //during normal processing, retry has precedence over skip
            if (needRetry(e)) {
                processingInfo.itemState = ItemState.TO_RETRY;
                return;
            } else if (needSkip(e)) {
                processingInfo.itemState = ItemState.TO_SKIP;
                return;
            }
        }
    }

    //already know need to retry, call this method to check if need to rollback before retry the current chunk
    private boolean needRollbackBeforeRetry(final Exception e) {
        //if no-rollback-exceptions not configured, by default need to rollback the current chunk
        //else if the current exception does not match the configured no-rollback-exceptions, need to rollback
        return noRollbackExceptionClasses == null || !noRollbackExceptionClasses.matches(e.getClass());
    }

    private void createChunkRelatedListeners() {
        final Listeners listeners = batchContext.getStep().getListeners();
        if (listeners == null) {
            return;
        }
        String ref;
        Object o;
        for (final RefArtifact l : listeners.getListeners()) {
            ref = l.getRef();
            Class<?> cls = null;
            if (stepRunner.chunkRelatedListeners != null) {
                cls = stepRunner.chunkRelatedListeners.get(ref);
            }
            o = jobContext.createArtifact(ref, cls, l.getProperties(), batchContext);
            allChunkRelatedListeners.add(o);
            if (o instanceof ChunkListener) {
                chunkListeners.add((ChunkListener) o);
            }
            if (o instanceof ItemReadListener) {
                itemReadListeners.add((ItemReadListener) o);
            }
            if (o instanceof ItemWriteListener) {
                itemWriteListeners.add((ItemWriteListener) o);
            }
            if (o instanceof ItemProcessListener) {
                itemProcessListeners.add((ItemProcessListener) o);
            }
            if (o instanceof SkipReadListener) {
                skipReadListeners.add((SkipReadListener) o);
            }
            if (o instanceof SkipWriteListener) {
                skipWriteListeners.add((SkipWriteListener) o);
            }
            if (o instanceof SkipProcessListener) {
                skipProcessListeners.add((SkipProcessListener) o);
            }
            if (o instanceof RetryReadListener) {
                retryReadListeners.add((RetryReadListener) o);
            }
            if (o instanceof RetryWriteListener) {
                retryWriteListeners.add((RetryWriteListener) o);
            }
            if (o instanceof RetryProcessListener) {
                retryProcessListeners.add((RetryProcessListener) o);
            }
        }
    }

    /**
     * Closes the reader and writer swallowing any exceptions
     */
    private void safeClose() {
        try {
            if (itemWriter != null) itemWriter.close();
        } catch (Exception e) {
            LOGGER.trace("Error closing ItemWriter.", e);
        }
        try {
            if (itemReader != null) itemReader.close();
        } catch (Exception e) {
            LOGGER.trace("Error closing ItemReader.", e);
        }
    }


    private static final class ProcessingInfo {
        /**
         * Number of items completed in during a checkpoint interval
         */
        int count;

        boolean timerExpired;
        ItemState itemState = ItemState.RUNNING;
        ChunkState chunkState = ChunkState.TO_START_NEW;

        /**
         * Used to remember the last checkpoint position in the reader intput data
         */
        int checkpointPosition;

        /**
         * Current position in the reader input data as 0-based int
         */
        int readPosition;

        /**
         * Where the failure occurred that caused the current retry.  The retry should stop after the item at failurePoint
         * has been retried.
         */
        Integer failurePoint;

        private void reset() {
            count = 0;
            timerExpired = false;
            itemState = ItemState.RUNNING;
            chunkState = ChunkState.RUNNING;
            failurePoint = null;
        }

        private boolean toStopItem() {
            return itemState == ItemState.TO_SKIP || itemState == ItemState.TO_RETRY ||
                    itemState == ItemState.TO_RETRY_READ || itemState == ItemState.TO_RETRY_PROCESS ||
                    itemState == ItemState.TO_RETRY_WRITE;
        }
    }

    private enum ItemState {
        RUNNING,       //normal item processing
        TO_SKIP,        //need to skip the remainder of the current iteration
        TO_RETRY,       //a super-type value for TO_RETRY_*, used to indicate whether to skip or retry current item

        TO_RETRY_READ,  //need to retry the current item read operation, upon starting next iteration => RETRYING_READ
        RETRYING_READ,  //the current item is being re-read, when successful or result in a skip => normal RUNNING

        TO_RETRY_PROCESS, //need to retry the current item process operation, upon starting next iteration => RETRYING_PROCESS
        RETRYING_PROCESS, //the current item is being re-processed, when successful or result in a skip => normal RUNNING

        TO_RETRY_WRITE, //need to retry the current item write operation, upon starting next items => RETRYING_WRITE
        RETRYING_WRITE  //the current item is being re-written, when successful or result in a skip => normal RUNNING
    }

    private enum ChunkState {
        RUNNING,      //normal running of chunk
        TO_RETRY,     //need to retry the current chunk
        RETRYING,     //chunk being retried
        TO_END_RETRY, //need to end retrying the current chunk
        TO_START_NEW, //the current chunk is done and need to start a new chunk next
        DEPLETED,      //no more input items, the processing can still go to next iteration so this last item can be retried

        JOB_STOPPING,  //the job has been requested to stop
        JOB_STOPPED
    }

}
TOP

Related Classes of org.jberet.runtime.runner.ChunkRunner

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.