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