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