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