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 }