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}