/** * @param skipLimit the number of skippable exceptions that are allowed to * be skipped * @param skippableExceptions exception classes that can be skipped * (non-critical) */ public LimitCheckingItemSkipPolicy(int skipLimit, Map<Class<? extends Throwable>, Boolean> skippableExceptions) { this(skipLimit, new BinaryExceptionClassifier(skippableExceptions)); }
@Override public boolean rollbackOn(Throwable ex) { return classifier.classify(ex); } });
@Override public Boolean classify(Throwable classifiable) { Boolean classified = super.classify(classifiable); if (!this.traverseCauses) { return classified; } /* * If the result is the default, we need to find out if it was by default * or so configured; if default, try the cause(es). */ if (classified.equals(this.getDefault())) { Throwable cause = classifiable; do { if (this.getClassified().containsKey(cause.getClass())) { return classified; // non-default classification } cause = cause.getCause(); classified = super.classify(cause); } while (cause != null && classified.equals(this.getDefault())); } return classified; }
/** * Create a {@link SimpleRetryPolicy} with the specified number of retry * attempts. If traverseCauses is true, the exception causes will be traversed until * a match is found. The default value indicates whether to retry or not for exceptions * (or super classes) are not found in the map. * * @param maxAttempts the maximum number of attempts * @param retryableExceptions the map of exceptions that are retryable based on the * map value (true/false). * @param traverseCauses is this clause traversable * @param defaultValue the default action. */ public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions, boolean traverseCauses, boolean defaultValue) { super(); this.maxAttempts = maxAttempts; this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions, defaultValue); this.retryableClassifier.setTraverseCauses(traverseCauses); }
/** * Create a binary exception classifier with the provided classes and their * subclasses. The mapped value for these exceptions will be the one * provided (which will be the opposite of the default). * * @param exceptionClasses the exceptions to classify among * @param value the value to classify */ public BinaryExceptionClassifier(Collection<Class<? extends Throwable>> exceptionClasses, boolean value) { this(!value); if (exceptionClasses != null) { Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>(); for (Class<? extends Throwable> type : exceptionClasses) { map.put(type, !getDefault()); } setTypeMap(map); } }
/** * Create a {@link SimpleRetryPolicy} with the specified number of retry * attempts. If traverseCauses is true, the exception causes will be traversed until * a match is found. The default value indicates whether to retry or not for exceptions * (or super classes) are not found in the map. * * @param maxAttempts the maximum number of attempts * @param retryableExceptions the map of exceptions that are retryable based on the * map value (true/false). * @param traverseCauses is this clause traversable * @param defaultValue the default action. */ public SimpleRetryPolicy(int maxAttempts, Map<Class<? extends Throwable>, Boolean> retryableExceptions, boolean traverseCauses, boolean defaultValue) { super(); this.maxAttempts = maxAttempts; this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions, defaultValue); this.retryableClassifier.setTraverseCauses(traverseCauses); }
/** * Create a binary exception classifier with the provided classes and their * subclasses. The mapped value for these exceptions will be the one * provided (which will be the opposite of the default). * * @param exceptionClasses the exceptions to classify among * @param value the value to classify */ public BinaryExceptionClassifier(Collection<Class<? extends Throwable>> exceptionClasses, boolean value) { this(!value); if (exceptionClasses != null) { Map<Class<? extends Throwable>, Boolean> map = new HashMap<Class<? extends Throwable>, Boolean>(); for (Class<? extends Throwable> type : exceptionClasses) { map.put(type, !getDefault()); } setTypeMap(map); } }
/** * Set up the classifier through a convenient map from throwable class to * boolean (true if skippable). * * @param skippableExceptions the skippable exceptions to set */ public void setSkippableExceptionMap(Map<Class<? extends Throwable>, Boolean> skippableExceptions) { this.skippableExceptionClassifier = new BinaryExceptionClassifier(skippableExceptions); }
/** * Delegates to an exception classifier. * * @param ex * @return true if this exception or its ancestors have been registered as * retryable. */ private boolean retryForException(Throwable ex) { return retryableClassifier.classify(ex); }
@Override public Boolean classify(Throwable classifiable) { Boolean classified = super.classify(classifiable); if (!this.traverseCauses) { return classified; } /* * If the result is the default, we need to find out if it was by default * or so configured; if default, try the cause(es). */ if (classified.equals(this.getDefault())) { Throwable cause = classifiable; do { if (this.getClassified().containsKey(cause.getClass())) { return classified; // non-default classification } cause = cause.getCause(); classified = super.classify(cause); } while (cause != null && classified.equals(this.getDefault())); } return classified; }
/** * Create an exception handler from its mandatory properties. * * @param retryPolicy the retry policy that will be under effect when an * exception is encountered * @param exceptionHandler the delegate to use if an exception actually * needs to be handled * @param fatalExceptionClasses exceptions */ public SimpleRetryExceptionHandler(RetryPolicy retryPolicy, ExceptionHandler exceptionHandler, Collection<Class<? extends Throwable>> fatalExceptionClasses) { this.retryPolicy = retryPolicy; this.exceptionHandler = exceptionHandler; this.fatalExceptionClassifier = new BinaryExceptionClassifier(fatalExceptionClasses); }
/** * Check if the exception is going to be retried, and veto the handling if * it is. If retry is exhausted or the exception is on the fatal list, then * handle using the delegate. * * @see ExceptionHandler#handleException(org.springframework.batch.repeat.RepeatContext, * java.lang.Throwable) */ @Override public void handleException(RepeatContext context, Throwable throwable) throws Throwable { // Only bother to check the delegate exception handler if we know that // retry is exhausted if (fatalExceptionClassifier.classify(throwable) || context.hasAttribute(EXHAUSTED)) { logger.debug("Handled fatal exception"); exceptionHandler.handleException(context, throwable); } else { logger.debug("Handled non-fatal exception", throwable); } }
/** * 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; }
@Override public boolean rollbackOn(Throwable ex) { return classifier.classify(ex); } });
@Override public StatefulRetryOperationsInterceptor build() { if (this.recoverer != null) { this.interceptor.setRecoverer(this.recoverer); } if (this.retryOperations != null) { this.interceptor.setRetryOperations(this.retryOperations); } else { this.interceptor.setRetryOperations(this.retryTemplate); } if (this.keyGenerator != null) { this.interceptor.setKeyGenerator(this.keyGenerator); } if (this.label != null) { this.interceptor.setLabel(this.label); } this.interceptor.setRollbackClassifier(new BinaryExceptionClassifier(false)); return this.interceptor; }
/** * Delegates to an exception classifier. * * @param ex * @return true if this exception or its ancestors have been registered as * retryable. */ private boolean retryForException(Throwable ex) { return retryableClassifier.classify(ex); }
@Test public void testTransformWithExceptionAndNoRollback() throws Exception { processor.setItemProcessor(new ItemProcessor<String, String>() { @Override public String process(String item) throws Exception { if (item.equals("1")) { throw new DataIntegrityViolationException("Planned"); } return item; } }); processor.setProcessSkipPolicy(new AlwaysSkipItemSkipPolicy()); processor .setRollbackClassifier(new BinaryExceptionClassifier( Collections .<Class<? extends Throwable>> singleton(DataIntegrityViolationException.class), false)); Chunk<String> inputs = new Chunk<>(Arrays.asList("1", "2")); processor.process(contribution, inputs); assertEquals(1, list.size()); }
@Override public boolean rollbackOn(Throwable ex) { return classifier.classify(ex); } });
processor.setRollbackClassifier(new BinaryExceptionClassifier(Collections .<Class<? extends Throwable>> singleton(IllegalArgumentException.class), false)); processor.afterPropertiesSet();
@Override public boolean rollbackOn(Throwable ex) { return classifier.classify(ex); } });