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