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