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    import java.util.TreeMap;
023    
024    import org.apache.camel.Exchange;
025    import org.apache.camel.model.OnExceptionDefinition;
026    import org.apache.camel.util.ObjectHelper;
027    import org.apache.commons.logging.Log;
028    import org.apache.commons.logging.LogFactory;
029    
030    /**
031     * The default strategy used in Camel to resolve the {@link org.apache.camel.model.OnExceptionDefinition} that should
032     * handle the thrown exception.
033     * <p/>
034     * <b>Selection strategy:</b>
035     * <br/>This strategy applies the following rules:
036     * <ul>
037     * <li>Will walk the exception hierarchy from bottom upwards till the thrown exception, meaning that the most outer caused
038     * by is selected first, ending with the thrown exception itself. The method {@link #createExceptionIterator(Throwable)}
039     * provides the Iterator used for the walking.</li>
040     * <li>The exception type must be configured with an Exception that is an instance of the thrown exception, this
041     * is tested using the {@link #filter(org.apache.camel.model.OnExceptionDefinition, Class, Throwable)} method.
042     * By default the filter uses <tt>instanceof</tt> test.</li>
043     * <li>If the exception type has <b>exactly</b> the thrown exception then its selected as its an exact match</li>
044     * <li>Otherwise the type that has an exception that is the closets super of the thrown exception is selected
045     * (recurring up the exception hierarchy)</li>
046     * </ul>
047     * <p/>
048     * <b>Fine grained matching:</b>
049     * <br/> If the {@link OnExceptionDefinition} has a when defined with an expression the type is also matches against
050     * the current exchange using the {@link #matchesWhen(org.apache.camel.model.OnExceptionDefinition, org.apache.camel.Exchange)}
051     * method. This can be used to for more fine grained matching, so you can e.g. define multiple sets of
052     * exception types with the same exception class(es) but have a predicate attached to select which to select at runtime.
053     */
054    public class DefaultExceptionPolicyStrategy implements ExceptionPolicyStrategy {
055    
056        private static final transient Log LOG = LogFactory.getLog(DefaultExceptionPolicyStrategy.class);
057    
058        public OnExceptionDefinition getExceptionPolicy(Map<ExceptionPolicyKey, OnExceptionDefinition> exceptionPolicies,
059                                                        Exchange exchange, Throwable exception) {
060    
061            Map<Integer, OnExceptionDefinition> candidates = new TreeMap<Integer, OnExceptionDefinition>();
062    
063            // recursive up the tree using the iterator
064            boolean exactMatch = false;
065            Iterator<Throwable> it = createExceptionIterator(exception);
066            while (!exactMatch && it.hasNext()) {
067                // we should stop looking if we have found an exact match
068                exactMatch = findMatchedExceptionPolicy(exceptionPolicies, exchange, it.next(), candidates);
069            }
070    
071            // now go through the candidates and find the best
072    
073            if (LOG.isTraceEnabled()) {
074                LOG.trace("Found " + candidates.size() + " candidates");
075            }
076    
077            if (candidates.isEmpty()) {
078                // no type found
079                return null;
080            } else {
081                // return the first in the map as its sorted and
082                return candidates.values().iterator().next();
083            }
084        }
085    
086    
087        private boolean findMatchedExceptionPolicy(Map<ExceptionPolicyKey, OnExceptionDefinition> exceptionPolicies,
088                                                                 Exchange exchange, Throwable exception,
089                                                                 Map<Integer, OnExceptionDefinition> candidates) {
090            if (LOG.isTraceEnabled()) {
091                LOG.trace("Finding best suited exception policy for thrown exception " + exception.getClass().getName());
092            }
093    
094            // the goal is to find the exception with the same/closet inheritance level as the target exception being thrown
095            int targetLevel = getInheritanceLevel(exception.getClass());
096            // candidate is the best candidate found so far to return
097            OnExceptionDefinition candidate = null;
098            // difference in inheritance level between the current candidate and the thrown exception (target level)
099            int candidateDiff = Integer.MAX_VALUE;
100    
101            // loop through all the entries and find the best candidates to use
102            Set<Map.Entry<ExceptionPolicyKey, OnExceptionDefinition>> entries = exceptionPolicies.entrySet();
103            for (Map.Entry<ExceptionPolicyKey, OnExceptionDefinition> entry : entries) {
104                Class<?> clazz = entry.getKey().getExceptionClass();
105                OnExceptionDefinition type = entry.getValue();
106    
107                if (filter(type, clazz, exception)) {
108    
109                    // must match
110                    if (!matchesWhen(type, exchange)) {
111                        if (LOG.isTraceEnabled()) {
112                            LOG.trace("The type did not match when: " + type);
113                        }
114                        continue;
115                    }
116    
117                    // exact match then break
118                    if (clazz.equals(exception.getClass())) {
119                        candidate = type;
120                        candidateDiff = 0;
121                        break;
122                    }
123    
124                    // not an exact match so find the best candidate
125                    int level = getInheritanceLevel(clazz);
126                    int diff = targetLevel - level;
127    
128                    if (diff < candidateDiff) {
129                        // replace with a much better candidate
130                        candidate = type;
131                        candidateDiff = diff;
132                    }
133                }
134            }
135    
136            if (candidate != null) {
137                if (!candidates.containsKey(candidateDiff)) {
138                    // only add as candidate if we do not already have it registered with that level
139                    if (LOG.isTraceEnabled()) {
140                        LOG.trace("Adding " + candidate + " as candidate at level " + candidateDiff);
141                    }
142                    candidates.put(candidateDiff, candidate);
143                } else {
144                    // we have an existing candidate already which we should prefer to use
145                    if (LOG.isTraceEnabled()) {
146                        LOG.trace("Existing candidate " + candidates.get(candidateDiff)
147                            + " takes precedence over " + candidate + " at level " + candidateDiff);
148                    }
149                }
150            }
151    
152            // if we found a exact match then we should stop continue looking
153            boolean exactMatch = candidateDiff == 0;
154            if (LOG.isTraceEnabled() && exactMatch) {
155                LOG.trace("Exact match found for candidate: " + candidate);
156            }
157            return exactMatch;
158        }
159    
160        /**
161         * Strategy to filter the given type exception class with the thrown exception
162         *
163         * @param type           the exception type
164         * @param exceptionClass the current exception class for testing
165         * @param exception      the thrown exception
166         * @return <tt>true</tt> if the to current exception class is a candidate, <tt>false</tt> to skip it.
167         */
168        protected boolean filter(OnExceptionDefinition type, Class<?> exceptionClass, Throwable exception) {
169            // must be instance of check to ensure that the exceptionClass is one type of the thrown exception
170            return exceptionClass.isInstance(exception);
171        }
172    
173        /**
174         * Strategy method for matching the exception type with the current exchange.
175         * <p/>
176         * This default implementation will match as:
177         * <ul>
178         * <li>Always true if no when predicate on the exception type
179         * <li>Otherwise the when predicate is matches against the current exchange
180         * </ul>
181         *
182         * @param definition     the exception definition
183         * @param exchange the current {@link Exchange}
184         * @return <tt>true</tt> if matched, <tt>false</tt> otherwise.
185         */
186        protected boolean matchesWhen(OnExceptionDefinition definition, Exchange exchange) {
187            if (definition.getOnWhen() == null || definition.getOnWhen().getExpression() == null) {
188                // if no predicate then it's always a match
189                return true;
190            }
191            return definition.getOnWhen().getExpression().matches(exchange);
192        }
193    
194        /**
195         * Strategy method creating the iterator to walk the exception in the order Camel should use
196         * for find the {@link OnExceptionDefinition} should be used.
197         * <p/>
198         * The default iterator will walk from the bottom upwards
199         * (the last caused by going upwards to the exception)
200         *
201         * @param exception  the exception
202         * @return the iterator
203         */
204        protected Iterator<Throwable> createExceptionIterator(Throwable exception) {
205            return ObjectHelper.createExceptionIterator(exception);
206        }
207    
208        private static int getInheritanceLevel(Class<?> clazz) {
209            if (clazz == null || "java.lang.Object".equals(clazz.getName())) {
210                return 0;
211            }
212            return 1 + getInheritanceLevel(clazz.getSuperclass());
213        }
214    }