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.reifier.errorhandler;
018
019import java.time.Duration;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.function.BiFunction;
025
026import org.apache.camel.CamelContext;
027import org.apache.camel.ErrorHandlerFactory;
028import org.apache.camel.ExtendedCamelContext;
029import org.apache.camel.LoggingLevel;
030import org.apache.camel.NamedNode;
031import org.apache.camel.Predicate;
032import org.apache.camel.Processor;
033import org.apache.camel.Route;
034import org.apache.camel.RuntimeCamelException;
035import org.apache.camel.builder.DeadLetterChannelBuilder;
036import org.apache.camel.builder.DefaultErrorHandlerBuilder;
037import org.apache.camel.builder.ErrorHandlerBuilder;
038import org.apache.camel.builder.ErrorHandlerBuilderRef;
039import org.apache.camel.builder.ErrorHandlerBuilderSupport;
040import org.apache.camel.builder.NoErrorHandlerBuilder;
041import org.apache.camel.model.OnExceptionDefinition;
042import org.apache.camel.model.RedeliveryPolicyDefinition;
043import org.apache.camel.processor.ErrorHandler;
044import org.apache.camel.processor.errorhandler.ErrorHandlerSupport;
045import org.apache.camel.processor.errorhandler.ExceptionPolicy;
046import org.apache.camel.processor.errorhandler.ExceptionPolicy.RedeliveryOption;
047import org.apache.camel.processor.errorhandler.ExceptionPolicyKey;
048import org.apache.camel.processor.errorhandler.RedeliveryErrorHandler;
049import org.apache.camel.processor.errorhandler.RedeliveryPolicy;
050import org.apache.camel.reifier.AbstractReifier;
051import org.apache.camel.spi.Language;
052import org.apache.camel.support.CamelContextHelper;
053import org.apache.camel.util.ObjectHelper;
054
055public abstract class ErrorHandlerReifier<T extends ErrorHandlerBuilderSupport> extends AbstractReifier {
056
057    private static final Map<Class<?>, BiFunction<Route, ErrorHandlerFactory, ErrorHandlerReifier<? extends ErrorHandlerFactory>>> ERROR_HANDLERS;
058    static {
059        Map<Class<?>, BiFunction<Route, ErrorHandlerFactory, ErrorHandlerReifier<? extends ErrorHandlerFactory>>> map = new HashMap<>();
060        map.put(DeadLetterChannelBuilder.class, DeadLetterChannelReifier::new);
061        map.put(DefaultErrorHandlerBuilder.class, DefaultErrorHandlerReifier::new);
062        map.put(ErrorHandlerBuilderRef.class, ErrorHandlerRefReifier::new);
063        map.put(NoErrorHandlerBuilder.class, NoErrorHandlerReifier::new);
064        ERROR_HANDLERS = map;
065    }
066
067    protected T definition;
068
069    /**
070     * Utility classes should not have a public constructor.
071     */
072    protected ErrorHandlerReifier(Route route, T definition) {
073        super(route);
074        this.definition = definition;
075    }
076
077    public static void registerReifier(Class<?> errorHandlerClass, BiFunction<Route, ErrorHandlerFactory, ErrorHandlerReifier<? extends ErrorHandlerFactory>> creator) {
078        ERROR_HANDLERS.put(errorHandlerClass, creator);
079    }
080
081    public static ErrorHandlerReifier<? extends ErrorHandlerFactory> reifier(Route route, ErrorHandlerFactory definition) {
082        BiFunction<Route, ErrorHandlerFactory, ErrorHandlerReifier<? extends ErrorHandlerFactory>> reifier = ERROR_HANDLERS.get(definition.getClass());
083        if (reifier != null) {
084            return reifier.apply(route, definition);
085        }
086        throw new IllegalStateException("Unsupported definition: " + definition);
087    }
088
089    public ExceptionPolicy createExceptionPolicy(OnExceptionDefinition def) {
090        Predicate handled = def.getHandledPolicy();
091        if (handled == null && def.getHandled() != null) {
092            handled = createPredicate(def.getHandled());
093        }
094        Predicate continued = def.getContinuedPolicy();
095        if (continued == null && def.getContinued() != null) {
096            continued = createPredicate(def.getContinued());
097        }
098        Predicate retryWhile = def.getRetryWhilePolicy();
099        if (retryWhile == null && def.getRetryWhile() != null) {
100            retryWhile = createPredicate(def.getRetryWhile());
101        }
102        Processor onRedelivery = getBean(Processor.class, def.getOnRedelivery(), def.getOnRedeliveryRef());
103        Processor onExceptionOccurred = getBean(Processor.class, def.getOnExceptionOccurred(), def.getOnExceptionOccurredRef());
104        return new ExceptionPolicy(def.getId(), CamelContextHelper.getRouteId(def),
105                                   parseBoolean(def.getUseOriginalMessage(), false),
106                                   parseBoolean(def.getUseOriginalBody(), false),
107                                   ObjectHelper.isNotEmpty(def.getOutputs()), handled,
108                                   continued, retryWhile, onRedelivery,
109                                   onExceptionOccurred, def.getRedeliveryPolicyRef(),
110                                   getRedeliveryPolicy(def.getRedeliveryPolicyType()), def.getExceptions());
111    }
112
113    private static Map<RedeliveryOption, String> getRedeliveryPolicy(RedeliveryPolicyDefinition definition) {
114        if (definition == null) {
115            return null;
116        }
117        Map<RedeliveryOption, String> policy = new HashMap<>();
118        setOption(policy, RedeliveryOption.maximumRedeliveries, definition.getMaximumRedeliveries());
119        setOption(policy, RedeliveryOption.redeliveryDelay, definition.getRedeliveryDelay());
120        setOption(policy, RedeliveryOption.asyncDelayedRedelivery, definition.getAsyncDelayedRedelivery());
121        setOption(policy, RedeliveryOption.backOffMultiplier, definition.getBackOffMultiplier());
122        setOption(policy, RedeliveryOption.useExponentialBackOff, definition.getUseExponentialBackOff());
123        setOption(policy, RedeliveryOption.collisionAvoidanceFactor, definition.getCollisionAvoidanceFactor());
124        setOption(policy, RedeliveryOption.useCollisionAvoidance, definition.getUseCollisionAvoidance());
125        setOption(policy, RedeliveryOption.maximumRedeliveryDelay, definition.getMaximumRedeliveryDelay());
126        setOption(policy, RedeliveryOption.retriesExhaustedLogLevel, definition.getRetriesExhaustedLogLevel());
127        setOption(policy, RedeliveryOption.retryAttemptedLogLevel, definition.getRetryAttemptedLogLevel());
128        setOption(policy, RedeliveryOption.retryAttemptedLogInterval, definition.getRetryAttemptedLogInterval());
129        setOption(policy, RedeliveryOption.logRetryAttempted, definition.getLogRetryAttempted());
130        setOption(policy, RedeliveryOption.logStackTrace, definition.getLogStackTrace());
131        setOption(policy, RedeliveryOption.logRetryStackTrace, definition.getLogRetryStackTrace());
132        setOption(policy, RedeliveryOption.logHandled, definition.getLogHandled());
133        setOption(policy, RedeliveryOption.logNewException, definition.getLogNewException());
134        setOption(policy, RedeliveryOption.logContinued, definition.getLogContinued());
135        setOption(policy, RedeliveryOption.logExhausted, definition.getLogExhausted());
136        setOption(policy, RedeliveryOption.logExhaustedMessageHistory, definition.getLogExhaustedMessageHistory());
137        setOption(policy, RedeliveryOption.logExhaustedMessageBody, definition.getLogExhaustedMessageBody());
138        setOption(policy, RedeliveryOption.disableRedelivery, definition.getDisableRedelivery());
139        setOption(policy, RedeliveryOption.delayPattern, definition.getDelayPattern());
140        setOption(policy, RedeliveryOption.allowRedeliveryWhileStopping, definition.getAllowRedeliveryWhileStopping());
141        setOption(policy, RedeliveryOption.exchangeFormatterRef, definition.getExchangeFormatterRef());
142        return policy;
143    }
144
145    private static void setOption(Map<RedeliveryOption, String> policy, RedeliveryOption option, Object value) {
146        if (value != null) {
147            policy.put(option, value.toString());
148        }
149    }
150
151    /**
152     * Lookup the error handler by the given ref
153     *
154     * @param route the route context
155     * @param ref reference id for the error handler
156     * @return the error handler
157     */
158    public static ErrorHandlerFactory lookupErrorHandlerFactory(Route route, String ref) {
159        return lookupErrorHandlerFactory(route, ref, true);
160    }
161
162    /**
163     * Lookup the error handler by the given ref
164     *
165     * @param route the route
166     * @param ref reference id for the error handler
167     * @param mandatory whether the error handler must exists, if not a
168     *            {@link org.apache.camel.NoSuchBeanException} is thrown
169     * @return the error handler
170     */
171    public static ErrorHandlerFactory lookupErrorHandlerFactory(Route route, String ref, boolean mandatory) {
172        ErrorHandlerFactory answer;
173        CamelContext camelContext = route.getCamelContext();
174
175        // if the ref is the default then we do not have any explicit error
176        // handler configured
177        // if that is the case then use error handlers configured on the route,
178        // as for instance
179        // the transacted error handler could have been configured on the route
180        // so we should use that one
181        if (!isErrorHandlerFactoryConfigured(ref)) {
182            // see if there has been configured a error handler builder on the route
183            answer = route.getErrorHandlerFactory();
184            // check if its also a ref with no error handler configuration like me
185            if (answer instanceof ErrorHandlerBuilderRef) {
186                ErrorHandlerBuilderRef other = (ErrorHandlerBuilderRef)answer;
187                String otherRef = other.getRef();
188                if (!isErrorHandlerFactoryConfigured(otherRef)) {
189                    // the other has also no explicit error handler configured
190                    // then fallback to the handler
191                    // configured on the parent camel context
192                    answer = lookupErrorHandlerFactory(camelContext);
193                }
194                if (answer == null) {
195                    // the other has also no explicit error handler configured
196                    // then fallback to the default error handler
197                    // otherwise we could recursive loop forever (triggered by
198                    // createErrorHandler method)
199                    answer = new DefaultErrorHandlerBuilder();
200                }
201                // inherit the error handlers from the other as they are to be
202                // shared
203                // this is needed by camel-spring when none error handler has
204                // been explicit configured
205                route.addErrorHandlerFactoryReference(other, answer);
206            }
207        } else {
208            // use specific configured error handler
209            if (mandatory) {
210                answer = CamelContextHelper.mandatoryLookup(camelContext, ref, ErrorHandlerBuilder.class);
211            } else {
212                answer = CamelContextHelper.lookup(camelContext, ref, ErrorHandlerBuilder.class);
213            }
214        }
215
216        return answer;
217    }
218
219    protected static ErrorHandlerFactory lookupErrorHandlerFactory(CamelContext camelContext) {
220        ErrorHandlerFactory answer = camelContext.adapt(ExtendedCamelContext.class).getErrorHandlerFactory();
221        if (answer instanceof ErrorHandlerBuilderRef) {
222            ErrorHandlerBuilderRef other = (ErrorHandlerBuilderRef)answer;
223            String otherRef = other.getRef();
224            if (isErrorHandlerFactoryConfigured(otherRef)) {
225                answer = CamelContextHelper.lookup(camelContext, otherRef, ErrorHandlerBuilder.class);
226                if (answer == null) {
227                    throw new IllegalArgumentException("ErrorHandlerBuilder with id " + otherRef + " not found in registry.");
228                }
229            }
230        }
231
232        return answer;
233    }
234
235    /**
236     * Returns whether a specific error handler builder has been configured or
237     * not.
238     * <p/>
239     * Can be used to test if none has been configured and then install a custom
240     * error handler builder replacing the default error handler (that would
241     * have been used as fallback otherwise). <br/>
242     * This is for instance used by the transacted policy to setup a
243     * TransactedErrorHandlerBuilder in camel-spring.
244     */
245    public static boolean isErrorHandlerFactoryConfigured(String ref) {
246        return !ErrorHandlerBuilderRef.DEFAULT_ERROR_HANDLER_BUILDER.equals(ref);
247    }
248
249    public void addExceptionPolicy(ErrorHandlerSupport handlerSupport, OnExceptionDefinition exceptionType) {
250        // add error handler as child service so they get lifecycle handled
251        Processor errorHandler = route.getOnException(exceptionType.getId());
252        handlerSupport.addErrorHandler(errorHandler);
253
254        // load exception classes
255        List<Class<? extends Throwable>> list;
256        if (ObjectHelper.isNotEmpty(exceptionType.getExceptions())) {
257            list = createExceptionClasses(exceptionType);
258            for (Class<? extends Throwable> clazz : list) {
259                String routeId = null;
260                // only get the route id, if the exception type is route scoped
261                if (exceptionType.isRouteScoped()) {
262                    routeId = route.getRouteId();
263                }
264                Predicate when = exceptionType.getOnWhen() != null ? exceptionType.getOnWhen().getExpression() : null;
265                ExceptionPolicyKey key = new ExceptionPolicyKey(routeId, clazz, when);
266                ExceptionPolicy policy = createExceptionPolicy(exceptionType);
267                handlerSupport.addExceptionPolicy(key, policy);
268            }
269        }
270    }
271
272    protected List<Class<? extends Throwable>> createExceptionClasses(OnExceptionDefinition exceptionType) {
273        List<String> list = exceptionType.getExceptions();
274        List<Class<? extends Throwable>> answer = new ArrayList<>(list.size());
275        for (String name : list) {
276            try {
277                Class<? extends Throwable> type = camelContext.getClassResolver().resolveMandatoryClass(name, Throwable.class);
278                answer.add(type);
279            } catch (ClassNotFoundException e) {
280                throw RuntimeCamelException.wrapRuntimeCamelException(e);
281            }
282        }
283        return answer;
284    }
285
286    /**
287     * Creates the error handler
288     *
289     * @param processor the outer processor
290     * @return the error handler
291     * @throws Exception is thrown if the error handler could not be created
292     */
293    public abstract Processor createErrorHandler(Processor processor) throws Exception;
294
295    public void configure(ErrorHandler handler) {
296        if (handler instanceof ErrorHandlerSupport) {
297            ErrorHandlerSupport handlerSupport = (ErrorHandlerSupport)handler;
298
299            for (NamedNode exception : route.getErrorHandlers(definition)) {
300                addExceptionPolicy(handlerSupport, (OnExceptionDefinition) exception);
301            }
302        }
303        if (handler instanceof RedeliveryErrorHandler) {
304            boolean original = ((RedeliveryErrorHandler)handler).isUseOriginalMessagePolicy() || ((RedeliveryErrorHandler)handler).isUseOriginalBodyPolicy();
305            if (original) {
306                // ensure allow original is turned on
307                route.setAllowUseOriginalMessage(true);
308            }
309        }
310    }
311
312    /**
313     * Note: Not for end users - this method is used internally by
314     * camel-blueprint
315     */
316    public static RedeliveryPolicy createRedeliveryPolicy(RedeliveryPolicyDefinition definition, CamelContext context, RedeliveryPolicy parentPolicy) {
317        RedeliveryPolicy answer;
318        if (parentPolicy != null) {
319            answer = parentPolicy.copy();
320        } else {
321            answer = new RedeliveryPolicy();
322        }
323
324        try {
325
326            // copy across the properties - if they are set
327            if (definition.getMaximumRedeliveries() != null) {
328                answer.setMaximumRedeliveries(CamelContextHelper.parseInteger(context, definition.getMaximumRedeliveries()));
329            }
330            if (definition.getRedeliveryDelay() != null) {
331                Duration duration = CamelContextHelper.parseDuration(context, definition.getRedeliveryDelay());
332                answer.setRedeliveryDelay(duration.toMillis());
333            }
334            if (definition.getAsyncDelayedRedelivery() != null) {
335                answer.setAsyncDelayedRedelivery(CamelContextHelper.parseBoolean(context, definition.getAsyncDelayedRedelivery()));
336            }
337            if (definition.getRetriesExhaustedLogLevel() != null) {
338                answer.setRetriesExhaustedLogLevel(CamelContextHelper.parse(context, LoggingLevel.class, definition.getRetriesExhaustedLogLevel()));
339            }
340            if (definition.getRetryAttemptedLogLevel() != null) {
341                answer.setRetryAttemptedLogLevel(CamelContextHelper.parse(context, LoggingLevel.class, definition.getRetryAttemptedLogLevel()));
342            }
343            if (definition.getRetryAttemptedLogInterval() != null) {
344                answer.setRetryAttemptedLogInterval(CamelContextHelper.parseInteger(context, definition.getRetryAttemptedLogInterval()));
345            }
346            if (definition.getBackOffMultiplier() != null) {
347                answer.setBackOffMultiplier(CamelContextHelper.parseDouble(context, definition.getBackOffMultiplier()));
348            }
349            if (definition.getUseExponentialBackOff() != null) {
350                answer.setUseExponentialBackOff(CamelContextHelper.parseBoolean(context, definition.getUseExponentialBackOff()));
351            }
352            if (definition.getCollisionAvoidanceFactor() != null) {
353                answer.setCollisionAvoidanceFactor(CamelContextHelper.parseDouble(context, definition.getCollisionAvoidanceFactor()));
354            }
355            if (definition.getUseCollisionAvoidance() != null) {
356                answer.setUseCollisionAvoidance(CamelContextHelper.parseBoolean(context, definition.getUseCollisionAvoidance()));
357            }
358            if (definition.getMaximumRedeliveryDelay() != null) {
359                Duration duration = CamelContextHelper.parseDuration(context, definition.getMaximumRedeliveryDelay());
360                answer.setMaximumRedeliveryDelay(duration.toMillis());
361            }
362            if (definition.getLogStackTrace() != null) {
363                answer.setLogStackTrace(CamelContextHelper.parseBoolean(context, definition.getLogStackTrace()));
364            }
365            if (definition.getLogRetryStackTrace() != null) {
366                answer.setLogRetryStackTrace(CamelContextHelper.parseBoolean(context, definition.getLogRetryStackTrace()));
367            }
368            if (definition.getLogHandled() != null) {
369                answer.setLogHandled(CamelContextHelper.parseBoolean(context, definition.getLogHandled()));
370            }
371            if (definition.getLogNewException() != null) {
372                answer.setLogNewException(CamelContextHelper.parseBoolean(context, definition.getLogNewException()));
373            }
374            if (definition.getLogContinued() != null) {
375                answer.setLogContinued(CamelContextHelper.parseBoolean(context, definition.getLogContinued()));
376            }
377            if (definition.getLogRetryAttempted() != null) {
378                answer.setLogRetryAttempted(CamelContextHelper.parseBoolean(context, definition.getLogRetryAttempted()));
379            }
380            if (definition.getLogExhausted() != null) {
381                answer.setLogExhausted(CamelContextHelper.parseBoolean(context, definition.getLogExhausted()));
382            }
383            if (definition.getLogExhaustedMessageHistory() != null) {
384                answer.setLogExhaustedMessageHistory(CamelContextHelper.parseBoolean(context, definition.getLogExhaustedMessageHistory()));
385            }
386            if (definition.getLogExhaustedMessageBody() != null) {
387                answer.setLogExhaustedMessageBody(CamelContextHelper.parseBoolean(context, definition.getLogExhaustedMessageBody()));
388            }
389            if (definition.getDisableRedelivery() != null) {
390                if (CamelContextHelper.parseBoolean(context, definition.getDisableRedelivery())) {
391                    answer.setMaximumRedeliveries(0);
392                }
393            }
394            if (definition.getDelayPattern() != null) {
395                answer.setDelayPattern(CamelContextHelper.parseText(context, definition.getDelayPattern()));
396            }
397            if (definition.getAllowRedeliveryWhileStopping() != null) {
398                answer.setAllowRedeliveryWhileStopping(CamelContextHelper.parseBoolean(context, definition.getAllowRedeliveryWhileStopping()));
399            }
400            if (definition.getExchangeFormatterRef() != null) {
401                answer.setExchangeFormatterRef(CamelContextHelper.parseText(context, definition.getExchangeFormatterRef()));
402            }
403        } catch (Exception e) {
404            throw RuntimeCamelException.wrapRuntimeCamelException(e);
405        }
406
407        return answer;
408    }
409
410    protected Predicate getPredicate(Predicate pred, String ref) {
411        if (pred == null && ref != null) {
412            // its a bean expression
413            Language bean = camelContext.resolveLanguage("bean");
414            pred = bean.createPredicate(ref);
415        }
416        return pred;
417    }
418
419    protected <T> T getBean(Class<T> clazz, T bean, String ref) {
420        if (bean == null && ref != null) {
421            bean = lookup(ref, clazz);
422        }
423        return bean;
424    }
425
426}