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 }