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.builder;
018
019import java.util.List;
020import java.util.concurrent.atomic.AtomicBoolean;
021
022import org.apache.camel.CamelContext;
023import org.apache.camel.Endpoint;
024import org.apache.camel.RoutesBuilder;
025import org.apache.camel.impl.DefaultCamelContext;
026import org.apache.camel.model.InterceptDefinition;
027import org.apache.camel.model.InterceptFromDefinition;
028import org.apache.camel.model.InterceptSendToEndpointDefinition;
029import org.apache.camel.model.ModelCamelContext;
030import org.apache.camel.model.OnCompletionDefinition;
031import org.apache.camel.model.OnExceptionDefinition;
032import org.apache.camel.model.RouteDefinition;
033import org.apache.camel.model.RoutesDefinition;
034import org.apache.camel.model.rest.RestConfigurationDefinition;
035import org.apache.camel.model.rest.RestDefinition;
036import org.apache.camel.model.rest.RestsDefinition;
037import org.apache.camel.spi.RestConfiguration;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * A <a href="http://camel.apache.org/dsl.html">Java DSL</a> which is
043 * used to build {@link org.apache.camel.impl.DefaultRoute} instances in a {@link CamelContext} for smart routing.
044 *
045 * @version 
046 */
047public abstract class RouteBuilder extends BuilderSupport implements RoutesBuilder {
048    protected Logger log = LoggerFactory.getLogger(getClass());
049    private AtomicBoolean initialized = new AtomicBoolean(false);
050    private RestsDefinition restCollection = new RestsDefinition();
051    private RestConfigurationDefinition restConfiguration;
052    private RoutesDefinition routeCollection = new RoutesDefinition();
053
054    public RouteBuilder() {
055        this(null);
056    }
057
058    public RouteBuilder(CamelContext context) {
059        super(context);
060    }
061
062    @Override
063    public String toString() {
064        return getRouteCollection().toString();
065    }
066
067    /**
068     * <b>Called on initialization to build the routes using the fluent builder syntax.</b>
069     * <p/>
070     * This is a central method for RouteBuilder implementations to implement
071     * the routes using the Java fluent builder syntax.
072     *
073     * @throws Exception can be thrown during configuration
074     */
075    public abstract void configure() throws Exception;
076
077    /**
078     * Configures the REST services
079     *
080     * @return the builder
081     */
082    public RestConfigurationDefinition restConfiguration() {
083        if (restConfiguration == null) {
084            restConfiguration = new RestConfigurationDefinition();
085        }
086        return restConfiguration;
087    }
088
089    /**
090     * Creates a new REST service
091     *
092     * @return the builder
093     */
094    public RestDefinition rest() {
095        getRestCollection().setCamelContext(getContext());
096        RestDefinition answer = getRestCollection().rest();
097        configureRest(answer);
098        return answer;
099    }
100
101    /**
102     * Creates a new REST service
103     *
104     * @param path  the base path
105     * @return the builder
106     */
107    public RestDefinition rest(String path) {
108        getRestCollection().setCamelContext(getContext());
109        RestDefinition answer = getRestCollection().rest(path);
110        configureRest(answer);
111        return answer;
112    }
113
114    /**
115     * Creates a new route from the given URI input
116     *
117     * @param uri  the from uri
118     * @return the builder
119     */
120    public RouteDefinition from(String uri) {
121        getRouteCollection().setCamelContext(getContext());
122        RouteDefinition answer = getRouteCollection().from(uri);
123        configureRoute(answer);
124        return answer;
125    }
126
127    /**
128     * Creates a new route from the given URI input
129     *
130     * @param uri  the String formatted from uri
131     * @param args arguments for the string formatting of the uri
132     * @return the builder
133     */
134    public RouteDefinition fromF(String uri, Object... args) {
135        getRouteCollection().setCamelContext(getContext());
136        RouteDefinition answer = getRouteCollection().from(String.format(uri, args));
137        configureRoute(answer);
138        return answer;
139    }
140
141    /**
142     * Creates a new route from the given endpoint
143     *
144     * @param endpoint  the from endpoint
145     * @return the builder
146     */
147    public RouteDefinition from(Endpoint endpoint) {
148        getRouteCollection().setCamelContext(getContext());
149        RouteDefinition answer = getRouteCollection().from(endpoint);
150        configureRoute(answer);
151        return answer;
152    }
153
154    /**
155     * Creates a new route from the given URIs input
156     *
157     * @param uris  the from uris
158     * @return the builder
159     */
160    public RouteDefinition from(String... uris) {
161        getRouteCollection().setCamelContext(getContext());
162        RouteDefinition answer = getRouteCollection().from(uris);
163        configureRoute(answer);
164        return answer;
165    }
166
167    /**
168     * Creates a new route from the given endpoint
169     *
170     * @param endpoints  the from endpoints
171     * @return the builder
172     */
173    public RouteDefinition from(Endpoint... endpoints) {
174        getRouteCollection().setCamelContext(getContext());
175        RouteDefinition answer = getRouteCollection().from(endpoints);
176        configureRoute(answer);
177        return answer;
178    }
179
180    /**
181     * Installs the given <a href="http://camel.apache.org/error-handler.html">error handler</a> builder
182     *
183     * @param errorHandlerBuilder  the error handler to be used by default for all child routes
184     */
185    public void errorHandler(ErrorHandlerBuilder errorHandlerBuilder) {
186        if (!getRouteCollection().getRoutes().isEmpty()) {
187            throw new IllegalArgumentException("errorHandler must be defined before any routes in the RouteBuilder");
188        }
189        getRouteCollection().setCamelContext(getContext());
190        setErrorHandlerBuilder(errorHandlerBuilder);
191    }
192
193    /**
194     * Adds a route for an interceptor that intercepts every processing step.
195     *
196     * @return the builder
197     */
198    public InterceptDefinition intercept() {
199        if (!getRouteCollection().getRoutes().isEmpty()) {
200            throw new IllegalArgumentException("intercept must be defined before any routes in the RouteBuilder");
201        }
202        getRouteCollection().setCamelContext(getContext());
203        return getRouteCollection().intercept();
204    }
205
206    /**
207     * Adds a route for an interceptor that intercepts incoming messages on any inputs in this route
208     *
209     * @return the builder
210     */
211    public InterceptFromDefinition interceptFrom() {
212        if (!getRouteCollection().getRoutes().isEmpty()) {
213            throw new IllegalArgumentException("interceptFrom must be defined before any routes in the RouteBuilder");
214        }
215        getRouteCollection().setCamelContext(getContext());
216        return getRouteCollection().interceptFrom();
217    }
218
219    /**
220     * Adds a route for an interceptor that intercepts incoming messages on the given endpoint.
221     *
222     * @param uri  endpoint uri
223     * @return the builder
224     */
225    public InterceptFromDefinition interceptFrom(String uri) {
226        if (!getRouteCollection().getRoutes().isEmpty()) {
227            throw new IllegalArgumentException("interceptFrom must be defined before any routes in the RouteBuilder");
228        }
229        getRouteCollection().setCamelContext(getContext());
230        return getRouteCollection().interceptFrom(uri);
231    }
232
233    /**
234     * Applies a route for an interceptor if an exchange is send to the given endpoint
235     *
236     * @param uri  endpoint uri
237     * @return the builder
238     */
239    public InterceptSendToEndpointDefinition interceptSendToEndpoint(String uri) {
240        if (!getRouteCollection().getRoutes().isEmpty()) {
241            throw new IllegalArgumentException("interceptSendToEndpoint must be defined before any routes in the RouteBuilder");
242        }
243        getRouteCollection().setCamelContext(getContext());
244        return getRouteCollection().interceptSendToEndpoint(uri);
245    }
246
247    /**
248     * <a href="http://camel.apache.org/exception-clause.html">Exception clause</a>
249     * for catching certain exceptions and handling them.
250     *
251     * @param exception exception to catch
252     * @return the builder
253     */
254    public OnExceptionDefinition onException(Class<? extends Throwable> exception) {
255        // is only allowed at the top currently
256        if (!getRouteCollection().getRoutes().isEmpty()) {
257            throw new IllegalArgumentException("onException must be defined before any routes in the RouteBuilder");
258        }
259        getRouteCollection().setCamelContext(getContext());
260        return getRouteCollection().onException(exception);
261    }
262
263    /**
264     * <a href="http://camel.apache.org/exception-clause.html">Exception clause</a>
265     * for catching certain exceptions and handling them.
266     *
267     * @param exceptions list of exceptions to catch
268     * @return the builder
269     */
270    public OnExceptionDefinition onException(Class<? extends Throwable>... exceptions) {
271        OnExceptionDefinition last = null;
272        for (Class<? extends Throwable> ex : exceptions) {
273            last = last == null ? onException(ex) : last.onException(ex);
274        }
275        return last != null ? last : onException(Exception.class);
276    }
277
278    /**
279     * <a href="http://camel.apache.org/oncompletion.html">On completion</a>
280     * callback for doing custom routing when the {@link org.apache.camel.Exchange} is complete.
281     *
282     * @return the builder
283     */
284    public OnCompletionDefinition onCompletion() {
285        // is only allowed at the top currently
286        if (!getRouteCollection().getRoutes().isEmpty()) {
287            throw new IllegalArgumentException("onCompletion must be defined before any routes in the RouteBuilder");
288        }
289        getRouteCollection().setCamelContext(getContext());
290        return getRouteCollection().onCompletion();
291    }
292    
293    // Properties
294    // -----------------------------------------------------------------------
295    public ModelCamelContext getContext() {
296        ModelCamelContext context = super.getContext();
297        if (context == null) {
298            context = createContainer();
299            setContext(context);
300        }
301        return context;
302    }
303
304    public void addRoutesToCamelContext(CamelContext context) throws Exception {
305        // must configure routes before rests
306        configureRoutes((ModelCamelContext) context);
307        configureRests((ModelCamelContext) context);
308
309        // but populate rests before routes, as we want to turn rests into routes
310        populateRests();
311        populateRoutes();
312    }
313
314    /**
315     * Configures the routes
316     *
317     * @param context the Camel context
318     * @return the routes configured
319     * @throws Exception can be thrown during configuration
320     */
321    public RoutesDefinition configureRoutes(ModelCamelContext context) throws Exception {
322        setContext(context);
323        checkInitialized();
324        routeCollection.setCamelContext(context);
325        return routeCollection;
326    }
327
328    /**
329     * Configures the rests
330     *
331     * @param context the Camel context
332     * @return the rests configured
333     * @throws Exception can be thrown during configuration
334     */
335    public RestsDefinition configureRests(ModelCamelContext context) throws Exception {
336        setContext(context);
337        restCollection.setCamelContext(context);
338        return restCollection;
339    }
340
341    /**
342     * Includes the routes from the build to this builder.
343     * <p/>
344     * This allows you to use other builds as route templates.
345     * @param routes other builder with routes to include
346     *
347     * @throws Exception can be thrown during configuration
348     */
349    public void includeRoutes(RoutesBuilder routes) throws Exception {
350        // TODO: We should support including multiple routes so I think invoking configure()
351        // needs to be deferred to later
352        if (routes instanceof RouteBuilder) {
353            // if its a RouteBuilder then let it use my route collection and error handler
354            // then we are integrated seamless
355            RouteBuilder builder = (RouteBuilder) routes;
356            builder.setContext(this.getContext());
357            builder.setRouteCollection(this.getRouteCollection());
358            builder.setErrorHandlerBuilder(this.getErrorHandlerBuilder());
359            // must invoke configure on the original builder so it adds its configuration to me
360            builder.configure();
361        } else {
362            getContext().addRoutes(routes);
363        }
364    }
365
366    @Override
367    public void setErrorHandlerBuilder(ErrorHandlerBuilder errorHandlerBuilder) {
368        super.setErrorHandlerBuilder(errorHandlerBuilder);
369        getRouteCollection().setErrorHandlerBuilder(getErrorHandlerBuilder());
370    }
371
372    // Implementation methods
373    // -----------------------------------------------------------------------
374    @SuppressWarnings("deprecation")
375    protected void checkInitialized() throws Exception {
376        if (initialized.compareAndSet(false, true)) {
377            // Set the CamelContext ErrorHandler here
378            ModelCamelContext camelContext = getContext();
379            if (camelContext.getErrorHandlerBuilder() != null) {
380                setErrorHandlerBuilder(camelContext.getErrorHandlerBuilder());
381            }
382            configure();
383            // mark all route definitions as custom prepared because
384            // a route builder prepares the route definitions correctly already
385            for (RouteDefinition route : getRouteCollection().getRoutes()) {
386                route.markPrepared();
387            }
388        }
389    }
390
391    protected void populateRoutes() throws Exception {
392        ModelCamelContext camelContext = getContext();
393        if (camelContext == null) {
394            throw new IllegalArgumentException("CamelContext has not been injected!");
395        }
396        getRouteCollection().setCamelContext(camelContext);
397        camelContext.addRouteDefinitions(getRouteCollection().getRoutes());
398    }
399
400    protected void populateRests() throws Exception {
401        ModelCamelContext camelContext = getContext();
402        if (camelContext == null) {
403            throw new IllegalArgumentException("CamelContext has not been injected!");
404        }
405        getRestCollection().setCamelContext(camelContext);
406
407        // setup rest configuration before adding the rests
408        if (getRestConfiguration() != null) {
409            RestConfiguration config = getRestConfiguration().asRestConfiguration(getContext());
410            camelContext.setRestConfiguration(config);
411        }
412        camelContext.addRestDefinitions(getRestCollection().getRests());
413
414        // convert rests into routes so we reuse routes for runtime
415        for (RestDefinition rest : getRestCollection().getRests()) {
416            List<RouteDefinition> routes = rest.asRouteDefinition(getContext());
417            for (RouteDefinition route : routes) {
418                getRouteCollection().route(route);
419            }
420        }
421    }
422
423    public RestsDefinition getRestCollection() {
424        return restCollection;
425    }
426
427    public RestConfigurationDefinition getRestConfiguration() {
428        return restConfiguration;
429    }
430
431    public void setRestCollection(RestsDefinition restCollection) {
432        this.restCollection = restCollection;
433    }
434
435    public void setRouteCollection(RoutesDefinition routeCollection) {
436        this.routeCollection = routeCollection;
437    }
438
439    public RoutesDefinition getRouteCollection() {
440        return this.routeCollection;
441    }
442
443    /**
444     * Factory method
445     *
446     * @return the CamelContext
447     */
448    protected ModelCamelContext createContainer() {
449        return new DefaultCamelContext();
450    }
451
452    protected void configureRest(RestDefinition rest) {
453        // noop
454    }
455
456    protected void configureRoute(RouteDefinition route) {
457        // noop
458    }
459
460    /**
461     * Adds a collection of routes to this context
462     *
463     * @param routes the routes
464     * @throws Exception if the routes could not be created for whatever reason
465     * @deprecated will be removed in Camel 3.0. Instead use {@link #includeRoutes(org.apache.camel.RoutesBuilder) includeRoutes} instead.
466     */
467    @Deprecated
468    protected void addRoutes(RoutesBuilder routes) throws Exception {
469        includeRoutes(routes);
470    }
471
472}