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.model;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.List;
022    
023    import javax.xml.bind.annotation.XmlAccessType;
024    import javax.xml.bind.annotation.XmlAccessorType;
025    import javax.xml.bind.annotation.XmlAttribute;
026    import javax.xml.bind.annotation.XmlElement;
027    import javax.xml.bind.annotation.XmlElementRef;
028    import javax.xml.bind.annotation.XmlRootElement;
029    import javax.xml.bind.annotation.XmlTransient;
030    
031    import org.apache.camel.CamelContext;
032    import org.apache.camel.Expression;
033    import org.apache.camel.LoggingLevel;
034    import org.apache.camel.Predicate;
035    import org.apache.camel.Processor;
036    import org.apache.camel.Route;
037    import org.apache.camel.builder.ErrorHandlerBuilder;
038    import org.apache.camel.builder.ExpressionBuilder;
039    import org.apache.camel.builder.ExpressionClause;
040    import org.apache.camel.processor.CatchProcessor;
041    import org.apache.camel.processor.RedeliveryPolicy;
042    import org.apache.camel.spi.RouteContext;
043    import org.apache.camel.util.CastUtils;
044    import org.apache.camel.util.ObjectHelper;
045    
046    import static org.apache.camel.builder.PredicateBuilder.toPredicate;
047    
048    /**
049     * Represents an XML <onException/> element
050     *
051     * @version $Revision: 896185 $
052     */
053    @XmlRootElement(name = "onException")
054    @XmlAccessorType(XmlAccessType.FIELD)
055    public class OnExceptionDefinition extends ProcessorDefinition<ProcessorDefinition> {
056    
057        @XmlElement(name = "exception")
058        private List<String> exceptions = new ArrayList<String>();
059        @XmlElement(name = "onWhen", required = false)
060        private WhenDefinition onWhen;
061        @XmlElement(name = "retryUntil", required = false)
062        private ExpressionSubElementDefinition retryUntil;
063        @XmlElement(name = "redeliveryPolicy", required = false)
064        private RedeliveryPolicyDefinition redeliveryPolicy;
065        @XmlElement(name = "handled", required = false)
066        private ExpressionSubElementDefinition handled;
067        @XmlAttribute(name = "onRedeliveryRef", required = false)
068        private String onRedeliveryRef;
069        @XmlAttribute(name = "useOriginalMessage", required = false)
070        private Boolean useOriginalMessagePolicy = Boolean.FALSE;
071        @XmlElementRef
072        private List<ProcessorDefinition> outputs = new ArrayList<ProcessorDefinition>();
073        @XmlTransient
074        private List<Class> exceptionClasses;
075        @XmlTransient
076        private Processor errorHandler;
077        @XmlTransient
078        private Predicate handledPolicy;
079        @XmlTransient
080        private Predicate retryUntilPolicy;
081        @XmlTransient
082        private Processor onRedelivery;
083    
084        public OnExceptionDefinition() {
085        }
086    
087        public OnExceptionDefinition(List<Class> exceptionClasses) {
088            this.exceptionClasses = CastUtils.cast(exceptionClasses);
089        }
090    
091        public OnExceptionDefinition(Class exceptionType) {
092            exceptionClasses = new ArrayList<Class>();
093            exceptionClasses.add(exceptionType);
094        }
095    
096        @Override
097        public String getShortName() {
098            return "onException";
099        }
100    
101        @Override
102        public String toString() {
103            return "OnException[" + getExceptionClasses() + (onWhen != null ? " " + onWhen : "") + " -> " + getOutputs() + "]";
104        }
105        
106        @Override
107        public boolean isAbstract() {
108            return true;
109        }
110    
111        /**
112         * Allows an exception handler to create a new redelivery policy for this exception type
113         * @param context the camel context
114         * @param parentPolicy the current redelivery policy
115         * @return a newly created redelivery policy, or return the original policy if no customization is required
116         * for this exception handler.
117         */
118        public RedeliveryPolicy createRedeliveryPolicy(CamelContext context, RedeliveryPolicy parentPolicy) {
119            if (redeliveryPolicy != null) {
120                return redeliveryPolicy.createRedeliveryPolicy(context, parentPolicy);
121            } else if (errorHandler != null) {
122                // lets create a new error handler that has no retries
123                RedeliveryPolicy answer = parentPolicy.copy();
124                answer.setMaximumRedeliveries(0);
125                return answer;
126            }
127            return parentPolicy;
128        }
129    
130        public void addRoutes(RouteContext routeContext, Collection<Route> routes) throws Exception {
131            setHandledFromExpressionType(routeContext);
132            setRetryUntilFromExpressionType(routeContext);
133            // lookup onRedelivery if ref is provided
134            if (ObjectHelper.isNotEmpty(onRedeliveryRef)) {
135                setOnRedelivery(routeContext.lookup(onRedeliveryRef, Processor.class));
136            }
137    
138            // lets attach this on exception to the route error handler
139            errorHandler = routeContext.createProcessor(this);
140            ErrorHandlerBuilder builder = routeContext.getRoute().getErrorHandlerBuilder();
141            builder.addErrorHandlers(this);
142        }
143    
144        @Override
145        public CatchProcessor createProcessor(RouteContext routeContext) throws Exception {
146            Processor childProcessor = routeContext.createProcessor(this);
147    
148            Predicate when = null;
149            if (onWhen != null) {
150                when = onWhen.getExpression().createPredicate(routeContext);
151            }
152    
153            Predicate handle = null;
154            if (handled != null) {
155                handle = handled.createPredicate(routeContext);
156            }
157    
158            return new CatchProcessor(getExceptionClasses(), childProcessor, when, handle);
159        }
160    
161    
162        // Fluent API
163        //-------------------------------------------------------------------------
164    
165        @Override
166        public OnExceptionDefinition onException(Class exceptionType) {
167            getExceptionClasses().add(exceptionType);
168            return this;
169        }
170    
171        /**
172         * Sets whether the exchange should be marked as handled or not.
173         *
174         * @param handled  handled or not
175         * @return the builder
176         */
177        public OnExceptionDefinition handled(boolean handled) {
178            Expression expression = ExpressionBuilder.constantExpression(Boolean.toString(handled));
179            return handled(expression);
180        }
181        
182        /**
183         * Sets whether the exchange should be marked as handled or not.
184         *
185         * @param handled  predicate that determines true or false
186         * @return the builder
187         */
188        public OnExceptionDefinition handled(Predicate handled) {
189            setHandledPolicy(handled);
190            return this;
191        }
192        
193        /**
194         * Sets whether the exchange should be marked as handled or not.
195         *
196         * @param handled  expression that determines true or false
197         * @return the builder
198         */
199        public OnExceptionDefinition handled(Expression handled) {
200            setHandledPolicy(toPredicate(handled));
201            return this;
202        }
203    
204        /**
205         * Sets an additional predicate that should be true before the onException is triggered.
206         * <p/>
207         * To be used for fine grained controlling whether a thrown exception should be intercepted
208         * by this exception type or not.
209         *
210         * @param predicate  predicate that determines true or false
211         * @return the builder
212         */
213        public OnExceptionDefinition onWhen(Predicate predicate) {
214            setOnWhen(new WhenDefinition(predicate));
215            return this;
216        }
217    
218        /**
219         * Creates an expression to configure an additional predicate that should be true before the
220         * onException is triggered.
221         * <p/>
222         * To be used for fine grained controlling whether a thrown exception should be intercepted
223         * by this exception type or not.
224         *
225         * @return the expression clause to configure
226         */
227        public ExpressionClause<OnExceptionDefinition> onWhen() {
228            onWhen = new WhenDefinition();
229            ExpressionClause<OnExceptionDefinition> clause = new ExpressionClause<OnExceptionDefinition>(this);
230            onWhen.setExpression(clause);
231            return clause;
232        }
233    
234        /**
235         * Sets the retry until predicate.
236         *
237         * @param until predicate that determines when to stop retrying
238         * @return the builder
239         */
240        public OnExceptionDefinition retryUntil(Predicate until) {
241            setRetryUntilPolicy(until);
242            return this;
243        }
244    
245        /**
246         * Sets the retry until expression.
247         *
248         * @param until expression that determines when to stop retrying
249         * @return the builder
250         */
251        public OnExceptionDefinition retryUntil(Expression until) {
252            setRetryUntilPolicy(toPredicate(until));
253            return this;
254        }
255    
256        /**
257         * Sets the delay
258         *
259         * @param delay  the redeliver delay
260         * @return the builder
261         */
262        public OnExceptionDefinition redeliverDelay(long delay) {
263            getOrCreateRedeliveryPolicy().redeliveryDelay(delay);
264            return this;
265        }
266    
267        /**
268         * Sets the back off multiplier
269         *
270         * @param backOffMultiplier  the back off multiplier
271         * @return the builder
272         */
273        public OnExceptionDefinition backOffMultiplier(double backOffMultiplier) {
274            getOrCreateRedeliveryPolicy().backOffMultiplier(backOffMultiplier);
275            return this;
276        }
277    
278        /**
279         * Sets the collision avoidance factor
280         *
281         * @param collisionAvoidanceFactor  the factor
282         * @return the builder
283         */
284        public OnExceptionDefinition collisionAvoidanceFactor(double collisionAvoidanceFactor) {
285            getOrCreateRedeliveryPolicy().collisionAvoidanceFactor(collisionAvoidanceFactor);
286            return this;
287        }
288    
289        /**
290         * Sets the collision avoidance percentage
291         *
292         * @param collisionAvoidancePercent  the percentage
293         * @return the builder
294         */
295        public OnExceptionDefinition collisionAvoidancePercent(double collisionAvoidancePercent) {
296            getOrCreateRedeliveryPolicy().collisionAvoidancePercent(collisionAvoidancePercent);
297            return this;
298        }
299    
300        /**
301         * Sets the fixed delay between redeliveries
302         *
303         * @param delay  delay in millis
304         * @return the builder
305         */
306        public OnExceptionDefinition redeliveryDelay(long delay) {
307            getOrCreateRedeliveryPolicy().redeliveryDelay(delay);
308            return this;
309        }
310    
311        /**
312         * Sets the logging level to use when retries has exhausted
313         *
314         * @param retriesExhaustedLogLevel  the logging level
315         * @return the builder
316         */
317        public OnExceptionDefinition retriesExhaustedLogLevel(LoggingLevel retriesExhaustedLogLevel) {
318            getOrCreateRedeliveryPolicy().retriesExhaustedLogLevel(retriesExhaustedLogLevel);
319            return this;
320        }
321    
322        /**
323         * Sets the logging level to use for logging retry attempts
324         *
325         * @param retryAttemptedLogLevel  the logging level
326         * @return the builder
327         */
328        public OnExceptionDefinition retryAttemptedLogLevel(LoggingLevel retryAttemptedLogLevel) {
329            getOrCreateRedeliveryPolicy().retryAttemptedLogLevel(retryAttemptedLogLevel);
330            return this;
331        }
332    
333        /**
334         * Sets the maximum redeliveries
335         * <ul>
336         *   <li>5 = default value</li>
337         *   <li>0 = no redeliveries</li>
338         *   <li>-1 = redeliver forever</li>
339         * </ul>
340         *
341         * @param maximumRedeliveries  the value
342         * @return the builder
343         */
344        public OnExceptionDefinition maximumRedeliveries(int maximumRedeliveries) {
345            getOrCreateRedeliveryPolicy().maximumRedeliveries(maximumRedeliveries);
346            return this;
347        }
348    
349        /**
350         * Turn on collision avoidance.
351         *
352         * @return the builder
353         */
354        public OnExceptionDefinition useCollisionAvoidance() {
355            getOrCreateRedeliveryPolicy().useCollisionAvoidance();
356            return this;
357        }
358    
359        /**
360         * Turn on exponential backk off
361         *
362         * @return the builder
363         */
364        public OnExceptionDefinition useExponentialBackOff() {
365            getOrCreateRedeliveryPolicy().useExponentialBackOff();
366            return this;
367        }
368    
369        /**
370         * Sets the maximum delay between redelivery
371         *
372         * @param maximumRedeliveryDelay  the delay in millis
373         * @return the builder
374         */
375        public OnExceptionDefinition maximumRedeliveryDelay(long maximumRedeliveryDelay) {
376            getOrCreateRedeliveryPolicy().maximumRedeliveryDelay(maximumRedeliveryDelay);
377            return this;
378        }
379    
380        /**
381         * Will use the original input body when an {@link org.apache.camel.Exchange} is moved to the dead letter queue.
382         * <p/>
383         * <b>Notice:</b> this only applies when all redeliveries attempt have failed and the {@link org.apache.camel.Exchange} is doomed for failure.
384         * <br/>
385         * Instead of using the current inprogress {@link org.apache.camel.Exchange} IN body we use the original IN body instead. This allows
386         * you to store the original input in the dead letter queue instead of the inprogress snapshot of the IN body.
387         * For instance if you route transform the IN body during routing and then failed. With the original exchange
388         * store in the dead letter queue it might be easier to manually re submit the {@link org.apache.camel.Exchange} again as the IN body
389         * is the same as when Camel received it. So you should be able to send the {@link org.apache.camel.Exchange} to the same input.
390         * <p/>
391         * By default this feature is off.
392         *
393         * @return the builder
394         */
395        public OnExceptionDefinition useOriginalBody() {
396            setUseOriginalMessagePolicy(Boolean.TRUE);
397            return this;
398        }
399    
400        /**
401         * Sets a processor that should be processed <b>before</b> a redelivey attempt.
402         * <p/>
403         * Can be used to change the {@link org.apache.camel.Exchange} <b>before</b> its being redelivered.
404         */
405        public OnExceptionDefinition onRedelivery(Processor processor) {
406            setOnRedelivery(processor);
407            return this;
408        }
409    
410        // Properties
411        //-------------------------------------------------------------------------
412        public List<ProcessorDefinition> getOutputs() {
413            return outputs;
414        }
415    
416        public void setOutputs(List<ProcessorDefinition> outputs) {
417            this.outputs = outputs;
418        }
419    
420        public List<Class> getExceptionClasses() {
421            if (exceptionClasses == null) {
422                exceptionClasses = createExceptionClasses();
423            }
424            return exceptionClasses;
425        }
426    
427        public void setExceptionClasses(List<Class> exceptionClasses) {
428            this.exceptionClasses = exceptionClasses;
429        }
430    
431        public List<String> getExceptions() {
432            return exceptions;
433        }
434    
435        public void setExceptions(List<String> exceptions) {
436            this.exceptions = exceptions;
437        }
438    
439        public Processor getErrorHandler() {
440            return errorHandler;
441        }
442    
443        public RedeliveryPolicyDefinition getRedeliveryPolicy() {
444            return redeliveryPolicy;
445        }
446    
447        public void setRedeliveryPolicy(RedeliveryPolicyDefinition redeliveryPolicy) {
448            this.redeliveryPolicy = redeliveryPolicy;
449        }
450    
451        public Predicate getHandledPolicy() {
452            return handledPolicy;
453        }
454    
455        public void setHandled(ExpressionSubElementDefinition handled) {
456            this.handled = handled;
457        }
458    
459        public ExpressionSubElementDefinition getHandled() {
460            return handled;
461        }    
462    
463        public void setHandledPolicy(Predicate handledPolicy) {
464            this.handledPolicy = handledPolicy;
465        }
466    
467        public WhenDefinition getOnWhen() {
468            return onWhen;
469        }
470    
471        public void setOnWhen(WhenDefinition onWhen) {
472            this.onWhen = onWhen;
473        }
474    
475        public ExpressionSubElementDefinition getRetryUntil() {
476            return retryUntil;
477        }
478    
479        public void setRetryUntil(ExpressionSubElementDefinition retryUntil) {
480            this.retryUntil = retryUntil;
481        }
482    
483        public Predicate getRetryUntilPolicy() {
484            return retryUntilPolicy;
485        }
486    
487        public void setRetryUntilPolicy(Predicate retryUntilPolicy) {
488            this.retryUntilPolicy = retryUntilPolicy;
489        }
490    
491        public Processor getOnRedelivery() {
492            return onRedelivery;
493        }
494    
495        public void setOnRedelivery(Processor onRedelivery) {
496            this.onRedelivery = onRedelivery;
497        }
498    
499        public String getOnRedeliveryRef() {
500            return onRedeliveryRef;
501        }
502    
503        public void setOnRedeliveryRef(String onRedeliveryRef) {
504            this.onRedeliveryRef = onRedeliveryRef;
505        }
506    
507        public Boolean getUseOriginalMessagePolicy() {
508            return useOriginalMessagePolicy;
509        }
510    
511        public void setUseOriginalMessagePolicy(Boolean useOriginalMessagePolicy) {
512            this.useOriginalMessagePolicy = useOriginalMessagePolicy;
513        }
514    
515        // Implementation methods
516        //-------------------------------------------------------------------------
517        protected RedeliveryPolicyDefinition getOrCreateRedeliveryPolicy() {
518            if (redeliveryPolicy == null) {
519                redeliveryPolicy = new RedeliveryPolicyDefinition();
520            }
521            return redeliveryPolicy;
522        }
523    
524        protected List<Class> createExceptionClasses() {
525            List<String> list = getExceptions();
526            List<Class> answer = new ArrayList<Class>(list.size());
527            for (String name : list) {
528                Class<Throwable> type = CastUtils.cast(ObjectHelper.loadClass(name, getClass().getClassLoader()), Throwable.class);
529                answer.add(type);
530            }
531            return answer;
532        }
533    
534    
535        private void setHandledFromExpressionType(RouteContext routeContext) {
536            if (getHandled() != null && handledPolicy == null && routeContext != null) {
537                handled(getHandled().createPredicate(routeContext));
538            }
539        }
540    
541        private void setRetryUntilFromExpressionType(RouteContext routeContext) {
542            if (getRetryUntil() != null && retryUntilPolicy == null && routeContext != null) {
543                retryUntil(getRetryUntil().createPredicate(routeContext));
544            }
545        }
546    }