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.model;
018
019import java.util.ArrayList;
020import java.util.List;
021
022import javax.xml.bind.annotation.XmlAccessType;
023import javax.xml.bind.annotation.XmlAccessorType;
024import javax.xml.bind.annotation.XmlElement;
025import javax.xml.bind.annotation.XmlElementRef;
026import javax.xml.bind.annotation.XmlRootElement;
027import javax.xml.bind.annotation.XmlTransient;
028
029import org.apache.camel.CamelContext;
030import org.apache.camel.Expression;
031import org.apache.camel.Predicate;
032import org.apache.camel.Processor;
033import org.apache.camel.builder.ExpressionBuilder;
034import org.apache.camel.processor.CatchProcessor;
035import org.apache.camel.spi.AsPredicate;
036import org.apache.camel.spi.Metadata;
037import org.apache.camel.spi.RouteContext;
038import org.apache.camel.util.ExpressionToPredicateAdapter;
039
040/**
041 * Catches exceptions as part of a try, catch, finally block
042 *
043 * @version 
044 */
045@Metadata(label = "error")
046@XmlRootElement(name = "doCatch")
047@XmlAccessorType(XmlAccessType.FIELD)
048public class CatchDefinition extends ProcessorDefinition<CatchDefinition> {
049    @XmlElement(name = "exception")
050    private List<String> exceptions = new ArrayList<>();
051    @XmlElement(name = "onWhen") @AsPredicate
052    private WhenDefinition onWhen;
053    @XmlElement(name = "handled") @AsPredicate
054    private ExpressionSubElementDefinition handled;
055    @XmlElementRef
056    private List<ProcessorDefinition<?>> outputs = new ArrayList<>();
057    @XmlTransient
058    private List<Class<? extends Throwable>> exceptionClasses;
059    @XmlTransient
060    private Predicate handledPolicy;
061
062    public CatchDefinition() {
063    }
064
065    public CatchDefinition(List<Class<? extends Throwable>> exceptionClasses) {
066        this.exceptionClasses = exceptionClasses;
067    }
068
069    public CatchDefinition(Class<? extends Throwable> exceptionType) {
070        exceptionClasses = new ArrayList<>();
071        exceptionClasses.add(exceptionType);
072    }
073
074    @Override
075    public String toString() {
076        return "DoCatch[ " + getExceptionClasses() + " -> " + getOutputs() + "]";
077    }
078
079    @Override
080    public String getShortName() {
081        return "doCatch";
082    }
083
084    @Override
085    public String getLabel() {
086        return "doCatch[ " + getExceptionClasses() + "]";
087    }
088
089    @Override
090    public CatchProcessor createProcessor(RouteContext routeContext) throws Exception {
091        // create and load exceptions if not done
092        if (exceptionClasses == null) {
093            exceptionClasses = createExceptionClasses(routeContext.getCamelContext());
094        }
095
096        // must have at least one exception
097        if (exceptionClasses.isEmpty()) {
098            throw new IllegalArgumentException("At least one Exception must be configured to catch");
099        }
100
101        // parent must be a try
102        if (!(getParent() instanceof TryDefinition)) {
103            throw new IllegalArgumentException("This doCatch should have a doTry as its parent on " + this);
104        }
105
106        // do catch does not mandate a child processor
107        Processor childProcessor = this.createChildProcessor(routeContext, false);
108
109        Predicate when = null;
110        if (onWhen != null) {
111            when = onWhen.getExpression().createPredicate(routeContext);
112        }
113
114        Predicate handle = handledPolicy;
115        if (handled != null) {
116            handle = handled.createPredicate(routeContext);
117        }
118
119        return new CatchProcessor(exceptionClasses, childProcessor, when, handle);
120    }
121
122    @Override
123    public List<ProcessorDefinition<?>> getOutputs() {
124        return outputs;
125    }
126
127    public void setOutputs(List<ProcessorDefinition<?>> outputs) {
128        this.outputs = outputs;
129    }
130
131    public boolean isOutputSupported() {
132        return true;
133    }
134
135    public List<Class<? extends Throwable>> getExceptionClasses() {
136        return exceptionClasses;
137    }
138
139    public void setExceptionClasses(List<Class<? extends Throwable>> exceptionClasses) {
140        this.exceptionClasses = exceptionClasses;
141    }
142    
143    // Fluent API
144    //-------------------------------------------------------------------------
145    /**
146     * The exceptions to catch.
147     *
148     * @param exceptionClasses  a list of the exception classes
149     * @return the builder
150     */
151    public CatchDefinition exceptionClasses(List<Class<? extends Throwable>> exceptionClasses) {
152        setExceptionClasses(exceptionClasses);
153        return this;
154    }
155
156    /**
157     * The exception(s) to catch.
158     *
159     * @param exceptions  one or more exceptions
160     * @return the builder
161     */
162    public CatchDefinition exception(Class<? extends Throwable>... exceptions) {
163        if (exceptionClasses == null) {
164            exceptionClasses = new ArrayList<>();
165        }
166        if (exceptions != null) {
167            for (Class<? extends Throwable> exception : exceptions) {
168                exceptionClasses.add(exception);
169            }
170        }
171        return this;
172    }
173    
174    /**
175     * Sets an additional predicate that should be true before the onCatch is triggered.
176     * <p/>
177     * To be used for fine grained controlling whether a thrown exception should be intercepted
178     * by this exception type or not.
179     *
180     * @param predicate  predicate that determines true or false
181     * @return the builder
182     */
183    public CatchDefinition onWhen(@AsPredicate Predicate predicate) {
184        setOnWhen(new WhenDefinition(predicate));
185        return this;
186    }
187
188    /**
189     * Sets whether the exchange should be marked as handled or not.
190     *
191     * @param handled  handled or not
192     * @return the builder
193     * @deprecated will be removed in Camel 3.0. Instead of using handled(false) you can re-throw the exception
194     * from a {@link Processor} or use the {@link ProcessorDefinition#throwException(Exception)}
195     */
196    @Deprecated
197    public CatchDefinition handled(boolean handled) {
198        Expression expression = ExpressionBuilder.constantExpression(Boolean.toString(handled));
199        return handled(expression);
200    }
201
202    /**
203     * Sets whether the exchange should be marked as handled or not.
204     *
205     * @param handled  predicate that determines true or false
206     * @return the builder
207     * @deprecated will be removed in Camel 3.0. Instead of using handled(false) you can re-throw the exception
208     * from a {@link Processor} or use the {@link ProcessorDefinition#throwException(Exception)}
209     */
210    @Deprecated
211    public CatchDefinition handled(@AsPredicate Predicate handled) {
212        setHandledPolicy(handled);
213        return this;
214    }
215
216    /**
217     * Sets whether the exchange should be marked as handled or not.
218     *
219     * @param handled  expression that determines true or false
220     * @return the builder
221     * @deprecated will be removed in Camel 3.0. Instead of using handled(false) you can re-throw the exception
222     * from a {@link Processor} or use the {@link ProcessorDefinition#throwException(Exception)}
223     */
224    @Deprecated
225    public CatchDefinition handled(@AsPredicate Expression handled) {
226        setHandledPolicy(ExpressionToPredicateAdapter.toPredicate(handled));
227        return this;
228    }
229
230    /**
231     * Sets the exception class that the CatchType want to catch
232     *
233     * @param exception  the exception of class
234     * @return the builder
235     */
236    public CatchDefinition exceptionClasses(Class<? extends Throwable> exception) {
237        List<Class<? extends Throwable>> list = getExceptionClasses();
238        list.add(exception);
239        return this;
240    }
241
242    public List<String> getExceptions() {
243        return exceptions;
244    }
245
246    public void setExceptions(List<String> exceptions) {
247        this.exceptions = exceptions;
248    }
249
250    public WhenDefinition getOnWhen() {
251        return onWhen;
252    }
253
254    public void setOnWhen(WhenDefinition onWhen) {
255        this.onWhen = onWhen;
256    }
257
258    public Predicate getHandledPolicy() {
259        return handledPolicy;
260    }
261
262    public void setHandledPolicy(Predicate handledPolicy) {
263        this.handledPolicy = handledPolicy;
264    }
265
266    public ExpressionSubElementDefinition getHandled() {
267        return handled;
268    }
269
270    public void setHandled(ExpressionSubElementDefinition handled) {
271        this.handled = handled;
272    }
273
274    protected List<Class<? extends Throwable>> createExceptionClasses(CamelContext context) throws ClassNotFoundException {
275        // must use the class resolver from CamelContext to load classes to ensure it can
276        // be loaded in all kind of environments such as JEE servers and OSGi etc.
277        List<String> list = getExceptions();
278        List<Class<? extends Throwable>> answer = new ArrayList<>(list.size());
279        for (String name : list) {
280            Class<Throwable> type = context.getClassResolver().resolveMandatoryClass(name, Throwable.class);
281            answer.add(type);
282        }
283        return answer;
284    }
285}