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 }