001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.processor.exceptionpolicy;
018    
019    import java.util.Iterator;
020    import java.util.Map;
021    import java.util.Set;
022    
023    import org.apache.camel.Exchange;
024    import org.apache.camel.model.OnExceptionDefinition;
025    import org.apache.camel.util.ObjectHelper;
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    
029    /**
030     * The default strategy used in Camel to resolve the {@link org.apache.camel.model.OnExceptionDefinition} that should
031     * handle the thrown exception.
032     * <p/>
033     * <b>Selection strategy:</b>
034     * <br/>This strategy applies the following rules:
035     * <ul>
036     * <li>Will walk the exception hieracy from bottom upwards till the thrown exception, meaning that the most outer caused
037     * by is selected first, ending with the thrown exception itself. The method {@link #createExceptionIterator(Throwable)}
038     * provides the Iterator used for the walking.</li>
039     * <li>The exception type must be configured with an Exception that is an instance of the thrown exception, this
040     * is tested using the {@link #filter(org.apache.camel.model.OnExceptionDefinition, Class, Throwable)} method.
041     * By default the filter uses <tt>instanceof</tt> test.</li>
042     * <li>If the exception type has <b>exactly</b> the thrown exception then its selected as its an exact match</li>
043     * <li>Otherwise the type that has an exception that is the closets super of the thrown exception is selected
044     * (recurring up the exception hierarchy)</li>
045     * </ul>
046     * <p/>
047     * <b>Fine grained matching:</b>
048     * <br/> If the {@link OnExceptionDefinition} has a when defined with an expression the type is also matches against
049     * the current exchange using the {@link #matchesWhen(org.apache.camel.model.OnExceptionDefinition, org.apache.camel.Exchange)}
050     * method. This can be used to for more fine grained matching, so you can e.g. define multiple sets of
051     * exception types with the same exception class(es) but have a predicate attached to select which to select at runtime.
052     */
053    public class DefaultExceptionPolicyStrategy implements ExceptionPolicyStrategy {
054    
055        private static final transient Log LOG = LogFactory.getLog(DefaultExceptionPolicyStrategy.class);
056    
057        public OnExceptionDefinition getExceptionPolicy(Map<ExceptionPolicyKey, OnExceptionDefinition> exceptionPolicices,
058                                                        Exchange exchange, Throwable exception) {
059    
060            // recursive up the tree using the iterator
061            Iterator<Throwable> it = createExceptionIterator(exception);
062            while (it.hasNext()) {
063                OnExceptionDefinition type = findMatchedExceptionPolicy(exceptionPolicices, exchange, it.next());
064                if (type != null) {
065                    return type;
066                }
067            }
068    
069            // no type found
070            return null;
071        }
072    
073    
074        private OnExceptionDefinition findMatchedExceptionPolicy(Map<ExceptionPolicyKey, OnExceptionDefinition> exceptionPolicices,
075                                                                 Exchange exchange, Throwable exception) {
076            if (LOG.isTraceEnabled()) {
077                LOG.trace("Finding best suited exception policy for thrown exception " + exception.getClass().getName());
078            }
079    
080            // the goal is to find the exception with the same/closet inheritance level as the target exception being thrown
081            int targetLevel = getInheritanceLevel(exception.getClass());
082            // candidate is the best candidate found so far to return
083            OnExceptionDefinition candidate = null;
084            // difference in inheritance level between the current candidate and the thrown exception (target level)
085            int candidateDiff = Integer.MAX_VALUE;
086    
087            // loop through all the entries and find the best candidates to use
088            Set<Map.Entry<ExceptionPolicyKey, OnExceptionDefinition>> entries = exceptionPolicices.entrySet();
089            for (Map.Entry<ExceptionPolicyKey, OnExceptionDefinition> entry : entries) {
090                Class clazz = entry.getKey().getExceptionClass();
091                OnExceptionDefinition type = entry.getValue();
092    
093                if (filter(type, clazz, exception)) {
094    
095                    // must match
096                    if (!matchesWhen(type, exchange)) {
097                        if (LOG.isTraceEnabled()) {
098                            LOG.trace("The type did not match when: " + type);
099                        }
100                        continue;
101                    }
102    
103                    // exact match then break
104                    if (clazz.equals(exception.getClass())) {
105                        candidate = type;
106                        break;
107                    }
108    
109                    // not an exact match so find the best candidate
110                    int level = getInheritanceLevel(clazz);
111                    int diff = targetLevel - level;
112    
113                    if (diff < candidateDiff) {
114                        // replace with a much better candidate
115                        candidate = type;
116                        candidateDiff = diff;
117                    }
118                }
119            }
120    
121            if (LOG.isTraceEnabled()) {
122                if (candidate != null) {
123                    LOG.trace("Using " + candidate + " as the exception policy");
124                } else {
125                    LOG.trace("No candidate found to be used as exception policy");
126                }
127            }
128    
129            return candidate;
130        }
131    
132        /**
133         * Strategy to filter the given type exception class with the thrown exception
134         *
135         * @param type           the exception type
136         * @param exceptionClass the current exception class for testing
137         * @param exception      the thrown exception
138         * @return <tt>true</tt> if the to current exception class is a candidate, <tt>false</tt> to skip it.
139         */
140        protected boolean filter(OnExceptionDefinition type, Class exceptionClass, Throwable exception) {
141            // must be instance of check to ensure that the exceptionClass is one type of the thrown exception
142            return exceptionClass.isInstance(exception);
143        }
144    
145        /**
146         * Strategy method for matching the exception type with the current exchange.
147         * <p/>
148         * This default implementation will match as:
149         * <ul>
150         * <li>Always true if no when predicate on the exception type
151         * <li>Otherwise the when predicate is matches against the current exchange
152         * </ul>
153         *
154         * @param definition     the exception definition
155         * @param exchange the current {@link Exchange}
156         * @return <tt>true</tt> if matched, <tt>false</tt> otherwise.
157         */
158        protected boolean matchesWhen(OnExceptionDefinition definition, Exchange exchange) {
159            if (definition.getOnWhen() == null || definition.getOnWhen().getExpression() == null) {
160                // if no predicate then it's always a match
161                return true;
162            }
163            return definition.getOnWhen().getExpression().matches(exchange);
164        }
165    
166        /**
167         * Strategy method creating the iterator to walk the exception in the order Camel should use
168         * for find the {@link OnExceptionDefinition} should be used.
169         * <p/>
170         * The default iterator will walk from the bottom upwards
171         * (the last caused by going upwards to the exception)
172         *
173         * @param exception  the exception
174         * @return the iterator
175         */
176        protected Iterator<Throwable> createExceptionIterator(Throwable exception) {
177            return ObjectHelper.createExceptionIterator(exception);
178        }
179    
180        private static int getInheritanceLevel(Class clazz) {
181            if (clazz == null || "java.lang.Object".equals(clazz.getName())) {
182                return 0;
183            }
184            return 1 + getInheritanceLevel(clazz.getSuperclass());
185        }
186    
187    }