001/* 002 * Copyright 2006-2007 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.batch.repeat.exception; 018 019import java.util.HashMap; 020import java.util.Map; 021import java.util.Map.Entry; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025import org.springframework.classify.Classifier; 026import org.springframework.classify.SubclassClassifier; 027import org.springframework.batch.repeat.RepeatContext; 028import org.springframework.batch.repeat.context.RepeatContextCounter; 029import org.springframework.util.ObjectUtils; 030 031/** 032 * Implementation of {@link ExceptionHandler} that rethrows when exceptions of a 033 * given type reach a threshold. Requires an {@link Classifier} that maps 034 * exception types to unique keys, and also a map from those keys to threshold 035 * values (Integer type). 036 * 037 * @author Dave Syer 038 * 039 */ 040public class RethrowOnThresholdExceptionHandler implements ExceptionHandler { 041 042 protected static final IntegerHolder ZERO = new IntegerHolder(0); 043 044 protected final Log logger = LogFactory.getLog(RethrowOnThresholdExceptionHandler.class); 045 046 private Classifier<? super Throwable, IntegerHolder> exceptionClassifier = (Classifier<Throwable, IntegerHolder>) classifiable -> ZERO; 047 048 private boolean useParent = false; 049 050 /** 051 * Flag to indicate the the exception counters should be shared between 052 * sibling contexts in a nested batch. Default is false. 053 * 054 * @param useParent true if the parent context should be used to store the 055 * counters. 056 */ 057 public void setUseParent(boolean useParent) { 058 this.useParent = useParent; 059 } 060 061 /** 062 * Set up the exception handler. Creates a default exception handler and 063 * threshold that maps all exceptions to a threshold of 0 - all exceptions 064 * are rethrown by default. 065 */ 066 public RethrowOnThresholdExceptionHandler() { 067 super(); 068 } 069 070 /** 071 * A map from exception classes to a threshold value of type Integer. 072 * 073 * @param thresholds the threshold value map. 074 */ 075 public void setThresholds(Map<Class<? extends Throwable>, Integer> thresholds) { 076 Map<Class<? extends Throwable>, IntegerHolder> typeMap = new HashMap<Class<? extends Throwable>, IntegerHolder>(); 077 for (Entry<Class<? extends Throwable>, Integer> entry : thresholds.entrySet()) { 078 typeMap.put(entry.getKey(), new IntegerHolder(entry.getValue())); 079 } 080 exceptionClassifier = new SubclassClassifier<>(typeMap, ZERO); 081 } 082 083 /** 084 * Classify the throwables and decide whether to re-throw based on the 085 * result. The context is used to accumulate the number of exceptions of the 086 * same type according to the classifier. 087 * 088 * @throws Throwable is thrown if number of exceptions exceeds threshold. 089 * @see ExceptionHandler#handleException(RepeatContext, Throwable) 090 */ 091 @Override 092 public void handleException(RepeatContext context, Throwable throwable) throws Throwable { 093 094 IntegerHolder key = exceptionClassifier.classify(throwable); 095 096 RepeatContextCounter counter = getCounter(context, key); 097 counter.increment(); 098 int count = counter.getCount(); 099 int threshold = key.getValue(); 100 if (count > threshold) { 101 throw throwable; 102 } 103 104 } 105 106 private RepeatContextCounter getCounter(RepeatContext context, IntegerHolder key) { 107 String attribute = RethrowOnThresholdExceptionHandler.class.getName() + "." + key; 108 // Creates a new counter and stores it in the correct context: 109 return new RepeatContextCounter(context, attribute, useParent); 110 } 111 112 /** 113 * @author Dave Syer 114 * 115 */ 116 private static class IntegerHolder { 117 118 private final int value; 119 120 /** 121 * @param value value within holder 122 */ 123 public IntegerHolder(int value) { 124 this.value = value; 125 } 126 127 /** 128 * Public getter for the value. 129 * @return the value 130 */ 131 public int getValue() { 132 return value; 133 } 134 135 /* 136 * (non-Javadoc) 137 * 138 * @see java.lang.Object#toString() 139 */ 140 @Override 141 public String toString() { 142 return ObjectUtils.getIdentityHexString(this) + "." + value; 143 } 144 145 } 146 147}