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