public boolean rollbackFor(Throwable exception) { if (rollbackClassifier == null) { return true; } return rollbackClassifier.classify(exception); }
@Override public boolean rollbackOn(Throwable ex) { return classifier.classify(ex); }
/** * Delegates to injected {@link ItemProcessor} instances according to the * classification by the {@link Classifier}. */ @Override public O process(I item) throws Exception { return processItem(classifier.classify(item), item); }
/** * {@inheritDoc} */ @SuppressWarnings("unchecked") public T classify(C classifiable) { if (classifier != null) { return classifier.classify(classifiable); } return (T) invoker.invokeMethod(classifiable); }
/** * Delegates to injected {@link ItemWriter} instances according to their * classification by the {@link Classifier}. */ @Override public void write(List<? extends T> items) throws Exception { Map<ItemWriter<? super T>, List<T>> map = new LinkedHashMap<>(); for (T item : items) { ItemWriter<? super T> key = classifier.classify(item); if (!map.containsKey(key)) { map.put(key, new ArrayList<>()); } map.get(key).add(item); } for (ItemWriter<? super T> writer : map.keySet()) { writer.write(map.get(writer)); } }
/** * Convenience method to get an exception classifier based on the provided transaction attributes. * * @return an exception classifier: maps to true if an exception should cause rollback */ protected Classifier<Throwable, Boolean> getRollbackClassifier() { Classifier<Throwable, Boolean> classifier = new BinaryExceptionClassifier(noRollbackExceptionClasses, false); // Try to avoid pathological cases where we cannot force a rollback // (should be pretty uncommon): if (!classifier.classify(new ForceRollbackForWriteSkipException("test", new RuntimeException())) || !classifier.classify(new ExhaustedRetryException("test"))) { final Classifier<Throwable, Boolean> binary = classifier; Collection<Class<? extends Throwable>> types = new HashSet<>(); types.add(ForceRollbackForWriteSkipException.class); types.add(ExhaustedRetryException.class); final Classifier<Throwable, Boolean> panic = new BinaryExceptionClassifier(types, true); classifier = (Classifier<Throwable, Boolean>) classifiable -> { // Rollback if either the user's list or our own applies return panic.classify(classifiable) || binary.classify(classifiable); }; } return classifier; }
/** * Given the provided exception and skip count, determine whether or not * processing should continue for the given exception. If the exception is * not classified as skippable in the classifier, false will be returned. If * the exception is classified as skippable and {@link StepExecution} * skipCount is greater than the skipLimit, then a * {@link SkipLimitExceededException} will be thrown. */ @Override public boolean shouldSkip(Throwable t, int skipCount) { if (skippableExceptionClassifier.classify(t)) { if (skipCount < skipLimit) { return true; } else { throw new SkipLimitExceededException(skipLimit, t); } } else { return false; } }
public void registerThrowable(RetryContext context, Throwable throwable) { policy = exceptionClassifier.classify(throwable); Assert.notNull(policy, "Could not locate policy for exception=[" + throwable + "]."); this.context = getContext(policy, context.getParent()); policy.registerThrowable(this.context, throwable); }
/** * Classify the throwables and decide whether to rethrow based on the * result. The context is not used. * * @throws Throwable thrown if {@link LogOrRethrowExceptionHandler#exceptionClassifier} * is classified as {@link Level#RETHROW}. * * @see ExceptionHandler#handleException(RepeatContext, Throwable) */ @Override public void handleException(RepeatContext context, Throwable throwable) throws Throwable { Level key = exceptionClassifier.classify(throwable); if (Level.ERROR.equals(key)) { logger.error("Exception encountered in batch repeat.", throwable); } else if (Level.WARN.equals(key)) { logger.warn("Exception encountered in batch repeat.", throwable); } else if (Level.DEBUG.equals(key) && logger.isDebugEnabled()) { logger.debug("Exception encountered in batch repeat.", throwable); } else if (Level.RETHROW.equals(key)) { throw throwable; } }
@Override public O recover(RetryContext context) throws Exception { Throwable e = context.getLastThrowable(); if (shouldSkip(skipPolicy, e, contribution.getStepSkipCount())) { contribution.incrementWriteSkipCount(); logger.debug("Skipping after failed write", e); return null; } else { if (rollbackClassifier.classify(e)) { // Default is to rollback unless the classifier // allows us to continue throw new RetryException("Non-skippable exception in recoverer while write", e); } return null; } }
@Override public I recover(RetryContext context) throws Exception { Throwable e = context.getLastThrowable(); if (shouldSkip(skipPolicy, e, contribution.getStepSkipCount())) { contribution.incrementReadSkipCount(); logger.debug("Skipping after failed process", e); return null; } else { if (rollbackClassifier.classify(e)) { // Default is to rollback unless the classifier // allows us to continue throw new RetryException("Non-skippable exception in recoverer while reading", e); } throw new BatchRuntimeException(e); } }
@Override public O recover(RetryContext context) throws Exception { Throwable e = context.getLastThrowable(); if (shouldSkip(skipPolicy, e, contribution.getStepSkipCount())) { contribution.incrementProcessSkipCount(); logger.debug("Skipping after failed process", e); return null; } else { if (rollbackClassifier.classify(e)) { // Default is to rollback unless the classifier // allows us to continue throw new RetryException("Non-skippable exception in recoverer while processing", e); } throw new BatchRuntimeException(e); } } };
/** * Classify the throwables and decide whether to re-throw based on the * result. The context is used to accumulate the number of exceptions of the * same type according to the classifier. * * @throws Throwable is thrown if number of exceptions exceeds threshold. * @see ExceptionHandler#handleException(RepeatContext, Throwable) */ @Override public void handleException(RepeatContext context, Throwable throwable) throws Throwable { IntegerHolder key = exceptionClassifier.classify(throwable); RepeatContextCounter counter = getCounter(context, key); counter.increment(); int count = counter.getCount(); int threshold = key.getValue(); if (count > threshold) { throw throwable; } }
@Override protected I read(StepContribution contribution, Chunk<I> chunk) throws Exception { while (true) { try { return doRead(); } catch (Exception e) { 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 (rollbackClassifier.classify(e)) { throw new NonSkippableReadException("Non-skippable exception during read", e); } logger.debug("No-rollback for non-skippable exception (ignored)", e); } } } }
@Override public O recover(RetryContext context) throws Exception { Throwable e = context.getLastThrowable(); if (shouldSkip(itemProcessSkipPolicy, e, contribution.getStepSkipCount())) { iterator.remove(e); contribution.incrementProcessSkipCount(); logger.debug("Skipping after failed process", e); return null; } else { if (rollbackClassifier.classify(e)) { // Default is to rollback unless the classifier // allows us to continue throw new RetryException("Non-skippable exception in recoverer while processing", e); } iterator.remove(e); return null; } }
@Override public Object recover(RetryContext context) throws Exception { Throwable e = context.getLastThrowable(); if (outputs.size() > 1 && !rollbackClassifier.classify(e)) { throw new RetryException("Invalid retry state during write caused by " + "exception that does not classify for rollback: ", e); } Chunk<I>.ChunkIterator inputIterator = inputs.iterator(); for (Chunk<O>.ChunkIterator outputIterator = outputs.iterator(); outputIterator.hasNext();) { inputIterator.next(); outputIterator.next(); checkSkipPolicy(inputIterator, outputIterator, e, contribution, true); if (!rollbackClassifier.classify(e)) { throw new RetryException( "Invalid retry state during recovery caused by exception that does not classify for rollback: ", e); } } return null; }
@Override public O doWithRetry(RetryContext context) throws Exception { try { return doTransform(item); } catch (Exception e) { if (shouldSkip(skipPolicy, e, contribution.getStepSkipCount())) { // If we are not re-throwing then we should check if // this is skippable contribution.incrementProcessSkipCount(); logger.debug("Skipping after failed process with no rollback", e); // If not re-throwing then the listener will not be // called in next chunk. getListener().onSkipInProcess(item, e); } else { getListener().onRetryProcessException(item, e); if (rollbackClassifier.classify(e)) { // Default is to rollback unless the classifier // allows us to continue throw e; } else { throw e; } } } return null; }
@Override public I doWithRetry(RetryContext arg0) throws Exception { while (true) { try { return doProvide(contribution, chunk); } catch (Exception e) { if (shouldSkip(skipPolicy, e, contribution.getStepSkipCount())) { // increment skip count and try again contribution.incrementReadSkipCount(); chunk.skip(e); getListener().onSkipInRead(e); logger.debug("Skipping failed input", e); } else { getListener().onRetryReadException(e); if(rollbackClassifier.classify(e)) { throw e; } else { throw e; } } } } } };
@Override public Object doWithRetry(RetryContext context) throws Exception { contextHolder.set(context); if (!data.scanning()) { chunkMonitor.setChunkSize(inputs.size()); try { doWrite(outputs.getItems()); } catch (Exception e) { if (rollbackClassifier.classify(e)) { throw e; } /* * If the exception is marked as no-rollback, we need to * override that, otherwise there's no way to write the * rest of the chunk or to honour the skip listener * contract. */ throw new ForceRollbackForWriteSkipException( "Force rollback on skippable exception so that skipped item can be located.", e); } contribution.incrementWriteCount(outputs.size()); } else { scan(contribution, inputs, outputs, chunkMonitor, false); } return null; } };