All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redis.spring.batch.step.FlushingFaultTolerantChunkProvider Maven / Gradle / Ivy

The newest version!
package com.redis.spring.batch.step;

import org.springframework.batch.core.StepContribution;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.core.step.item.SkipOverflowException;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.core.step.skip.NonSkippableReadException;
import org.springframework.batch.core.step.skip.SkipException;
import org.springframework.batch.core.step.skip.SkipListenerFailedException;
import org.springframework.batch.core.step.skip.SkipPolicy;
import org.springframework.batch.core.step.skip.SkipPolicyFailedException;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.repeat.RepeatOperations;
import org.springframework.classify.BinaryExceptionClassifier;
import org.springframework.classify.Classifier;

/**
 * Fault-tolerant implementation of the ChunkProvider interface, that allows for skipping or retry of items that cause
 * exceptions, as well as incomplete chunks when timeout is reached.
 */
public class FlushingFaultTolerantChunkProvider extends FlushingChunkProvider {

    /**
     * Hard limit for number of read skips in the same chunk. Should be sufficiently high that it is only encountered in a
     * runaway step where all items are skipped before the chunk can complete (leading to a potential heap memory problem).
     */
    public static final int DEFAULT_MAX_SKIPS_ON_READ = 100;

    private SkipPolicy skipPolicy = new LimitCheckingItemSkipPolicy();

    private Classifier rollbackClassifier = new BinaryExceptionClassifier(true);

    private int maxSkipsOnRead = DEFAULT_MAX_SKIPS_ON_READ;

    public FlushingFaultTolerantChunkProvider(ItemReader itemReader, RepeatOperations repeatOperations) {
        super(itemReader, repeatOperations);
    }

    /**
     * @param maxSkipsOnRead the maximum number of skips on read
     */
    public void setMaxSkipsOnRead(int maxSkipsOnRead) {
        this.maxSkipsOnRead = maxSkipsOnRead;
    }

    /**
     * The policy that determines whether exceptions can be skipped on read.
     *
     * @param skipPolicy instance of {@link SkipPolicy} to be used by FaultTolerantChunkProvider.
     */
    public void setSkipPolicy(SkipPolicy skipPolicy) {
        this.skipPolicy = skipPolicy;
    }

    /**
     * Classifier to determine whether exceptions have been marked as no-rollback (as opposed to skippable). If encountered they
     * are simply ignored, unless also skippable.
     *
     * @param rollbackClassifier the rollback classifier to set
     */
    public void setRollbackClassifier(Classifier rollbackClassifier) {
        this.rollbackClassifier = rollbackClassifier;
    }

    @Override
    protected I read(StepContribution contribution, Chunk chunk, long timeout) throws InterruptedException {
        while (true) {
            try {
                return doRead(timeout);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw e;
            } catch (Exception e) {
                handleException(e, contribution, chunk);
            }
        }
    }

    private void handleException(Exception e, StepContribution contribution, Chunk chunk) {
        if (shouldSkip(skipPolicy, e, contribution.getStepSkipCount())) {

            // increment skip count and try again
            contribution.incrementReadSkipCount();
            chunk.skip(e);

            if (chunk.getErrors().size() >= maxSkipsOnRead) {
                throw new SkipOverflowException("Too many skips on read");
            }

            logger.debug("Skipping failed input", e);
        } else {
            if (Boolean.TRUE.equals(rollbackClassifier.classify(e))) {
                throw new NonSkippableReadException("Non-skippable exception during read", e);
            }
            logger.debug("No-rollback for non-skippable exception (ignored)", e);
        }

    }

    /**
     * Convenience method for calling process skip policy.
     *
     * @param policy the skip policy
     * @param e the cause of the skip
     * @param skipCount the current skip count
     */
    private boolean shouldSkip(SkipPolicy policy, Throwable e, long skipCount) {
        try {
            return policy.shouldSkip(e, skipCount);
        } catch (SkipException ex) {
            throw ex;
        } catch (RuntimeException ex) {
            throw new SkipPolicyFailedException("Fatal exception in SkipPolicy.", ex, e);
        }
    }

    @Override
    public void postProcess(StepContribution contribution, Chunk chunk) {
        for (Exception e : chunk.getErrors()) {
            try {
                getListener().onSkipInRead(e);
            } catch (RuntimeException ex) {
                throw new SkipListenerFailedException("Fatal exception in SkipListener.", ex, e);
            }
        }
    }

}