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; 018 019import java.util.ArrayDeque; 020import java.util.Deque; 021 022import org.apache.camel.AsyncCallback; 023import org.apache.camel.Exchange; 024import org.apache.camel.Processor; 025import org.slf4j.Logger; 026import org.slf4j.LoggerFactory; 027 028import static org.apache.camel.builder.ExpressionBuilder.routeIdExpression; 029 030/** 031 * An {@link org.apache.camel.processor.ErrorHandler} used as a safe fallback when 032 * processing by other error handlers such as the {@link org.apache.camel.model.OnExceptionDefinition}. 033 * <p/> 034 * This error handler is used as a fail-safe to ensure that error handling does not run in endless recursive looping 035 * which potentially can happen if a new exception is thrown while error handling a previous exception which then 036 * cause new error handling to process and this then keep on failing with new exceptions in an endless loop. 037 * 038 * @version 039 */ 040public class FatalFallbackErrorHandler extends DelegateAsyncProcessor implements ErrorHandler { 041 042 private static final Logger LOG = LoggerFactory.getLogger(FatalFallbackErrorHandler.class); 043 044 private boolean deadLetterChannel; 045 046 public FatalFallbackErrorHandler(Processor processor) { 047 this(processor, false); 048 } 049 050 public FatalFallbackErrorHandler(Processor processor, boolean isDeadLetterChannel) { 051 super(processor); 052 this.deadLetterChannel = isDeadLetterChannel; 053 } 054 055 @Override 056 @SuppressWarnings("unchecked") 057 public boolean process(final Exchange exchange, final AsyncCallback callback) { 058 // get the current route id we use 059 final String id = routeIdExpression().evaluate(exchange, String.class); 060 061 // prevent endless looping if we end up coming back to ourself 062 Deque<String> fatals = exchange.getProperty(Exchange.FATAL_FALLBACK_ERROR_HANDLER, null, Deque.class); 063 if (fatals == null) { 064 fatals = new ArrayDeque<>(); 065 exchange.setProperty(Exchange.FATAL_FALLBACK_ERROR_HANDLER, fatals); 066 } 067 if (fatals.contains(id)) { 068 LOG.warn("Circular error-handler detected at route: {} - breaking out processing Exchange: {}", id, exchange); 069 // mark this exchange as already been error handler handled (just by having this property) 070 // the false value mean the caught exception will be kept on the exchange, causing the 071 // exception to be propagated back to the caller, and to break out routing 072 exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, false); 073 exchange.setProperty(Exchange.ERRORHANDLER_CIRCUIT_DETECTED, true); 074 callback.done(true); 075 return true; 076 } 077 078 // okay we run under this fatal error handler now 079 fatals.push(id); 080 081 // support the asynchronous routing engine 082 boolean sync = processor.process(exchange, new AsyncCallback() { 083 public void done(boolean doneSync) { 084 try { 085 if (exchange.getException() != null) { 086 // an exception occurred during processing onException 087 088 // log detailed error message with as much detail as possible 089 Throwable previous = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Throwable.class); 090 091 // check if previous and this exception are set as the same exception 092 // which happens when using global scoped onException and you call a direct route that causes the 2nd exception 093 // then we need to find the original previous exception as the suppressed exception 094 if (previous != null && previous == exchange.getException()) { 095 previous = null; 096 // maybe previous was suppressed? 097 if (exchange.getException().getSuppressed().length > 0) { 098 previous = exchange.getException().getSuppressed()[0]; 099 } 100 } 101 102 String msg = "Exception occurred while trying to handle previously thrown exception on exchangeId: " 103 + exchange.getExchangeId() + " using: [" + processor + "]."; 104 if (previous != null) { 105 msg += " The previous and the new exception will be logged in the following."; 106 log(msg); 107 log("\\--> Previous exception on exchangeId: " + exchange.getExchangeId(), previous); 108 log("\\--> New exception on exchangeId: " + exchange.getExchangeId(), exchange.getException()); 109 } else { 110 log(msg); 111 log("\\--> New exception on exchangeId: " + exchange.getExchangeId(), exchange.getException()); 112 } 113 114 // add previous as suppressed to exception if not already there 115 if (previous != null) { 116 Throwable[] suppressed = exchange.getException().getSuppressed(); 117 boolean found = false; 118 for (Throwable t : suppressed) { 119 if (t == previous) { 120 found = true; 121 } 122 } 123 if (!found) { 124 exchange.getException().addSuppressed(previous); 125 } 126 } 127 128 // we can propagated that exception to the caught property on the exchange 129 // which will shadow any previously caught exception and cause this new exception 130 // to be visible in the error handler 131 exchange.setProperty(Exchange.EXCEPTION_CAUGHT, exchange.getException()); 132 133 if (deadLetterChannel) { 134 // special for dead letter channel as we want to let it determine what to do, depending how 135 // it has been configured 136 exchange.removeProperty(Exchange.ERRORHANDLER_HANDLED); 137 } else { 138 // mark this exchange as already been error handler handled (just by having this property) 139 // the false value mean the caught exception will be kept on the exchange, causing the 140 // exception to be propagated back to the caller, and to break out routing 141 exchange.setProperty(Exchange.ERRORHANDLER_HANDLED, false); 142 } 143 } 144 } finally { 145 // no longer running under this fatal fallback error handler 146 Deque<String> fatals = exchange.getProperty(Exchange.FATAL_FALLBACK_ERROR_HANDLER, null, Deque.class); 147 if (fatals != null) { 148 fatals.removeLastOccurrence(id); 149 } 150 callback.done(doneSync); 151 } 152 } 153 }); 154 155 return sync; 156 } 157 158 private void log(String message) { 159 log(message, null); 160 } 161 162 private void log(String message, Throwable t) { 163 // when using dead letter channel we only want to log at WARN level 164 if (deadLetterChannel) { 165 if (t != null) { 166 LOG.warn(message, t); 167 } else { 168 LOG.warn(message); 169 } 170 } else { 171 if (t != null) { 172 LOG.error(message, t); 173 } else { 174 LOG.error(message); 175 } 176 } 177 } 178 179 @Override 180 public String toString() { 181 return "FatalFallbackErrorHandler[" + processor + "]"; 182 } 183}