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.Collection;
021import java.util.List;
022import java.util.StringTokenizer;
023import java.util.concurrent.atomic.AtomicBoolean;
024import javax.xml.bind.annotation.XmlAccessType;
025import javax.xml.bind.annotation.XmlAccessorType;
026import javax.xml.bind.annotation.XmlAttribute;
027import javax.xml.bind.annotation.XmlElement;
028import javax.xml.bind.annotation.XmlElementRef;
029import javax.xml.bind.annotation.XmlRootElement;
030import javax.xml.bind.annotation.XmlTransient;
031import javax.xml.bind.annotation.XmlType;
032
033import org.apache.camel.CamelContext;
034import org.apache.camel.Endpoint;
035import org.apache.camel.ErrorHandlerFactory;
036import org.apache.camel.FailedToCreateRouteException;
037import org.apache.camel.NoSuchEndpointException;
038import org.apache.camel.Route;
039import org.apache.camel.ServiceStatus;
040import org.apache.camel.ShutdownRoute;
041import org.apache.camel.ShutdownRunningTask;
042import org.apache.camel.StatefulService;
043import org.apache.camel.builder.AdviceWithRouteBuilder;
044import org.apache.camel.builder.AdviceWithTask;
045import org.apache.camel.builder.ErrorHandlerBuilderRef;
046import org.apache.camel.builder.RouteBuilder;
047import org.apache.camel.impl.DefaultRouteContext;
048import org.apache.camel.model.rest.RestBindingDefinition;
049import org.apache.camel.model.rest.RestDefinition;
050import org.apache.camel.processor.interceptor.HandleFault;
051import org.apache.camel.spi.AsEndpointUri;
052import org.apache.camel.spi.LifecycleStrategy;
053import org.apache.camel.spi.Metadata;
054import org.apache.camel.spi.RouteContext;
055import org.apache.camel.spi.RoutePolicy;
056import org.apache.camel.spi.RoutePolicyFactory;
057import org.apache.camel.spi.Transformer;
058import org.apache.camel.spi.Validator;
059import org.apache.camel.util.CamelContextHelper;
060import org.apache.camel.util.ObjectHelper;
061
062/**
063 * A Camel route
064 *
065 * @version
066 */
067@Metadata(label = "configuration")
068@XmlRootElement(name = "route")
069@XmlType(propOrder = {"inputs", "inputType", "outputType", "outputs", "routeProperties"})
070@XmlAccessorType(XmlAccessType.PROPERTY)
071// must use XmlAccessType.PROPERTY as there is some custom logic needed to be executed in the setter methods
072public class RouteDefinition extends ProcessorDefinition<RouteDefinition> {
073    private final AtomicBoolean prepared = new AtomicBoolean(false);
074    private List<FromDefinition> inputs = new ArrayList<>();
075    private List<ProcessorDefinition<?>> outputs = new ArrayList<>();
076    private String group;
077    private String streamCache;
078    private String trace;
079    private String messageHistory;
080    private String logMask;
081    private String handleFault;
082    private String delayer;
083    private String autoStartup;
084    private Integer startupOrder;
085    private List<RoutePolicy> routePolicies;
086    private String routePolicyRef;
087    private ShutdownRoute shutdownRoute;
088    private ShutdownRunningTask shutdownRunningTask;
089    private String errorHandlerRef;
090    private ErrorHandlerFactory errorHandlerBuilder;
091    // keep state whether the error handler is context scoped or not
092    // (will by default be context scoped of no explicit error handler configured)
093    private boolean contextScopedErrorHandler = true;
094    private Boolean rest;
095    private RestDefinition restDefinition;
096    private RestBindingDefinition restBindingDefinition;
097    private InputTypeDefinition inputType;
098    private OutputTypeDefinition outputType;
099    private List<PropertyDefinition> routeProperties;
100
101    public RouteDefinition() {
102    }
103
104    public RouteDefinition(@AsEndpointUri String uri) {
105        from(uri);
106    }
107
108    public RouteDefinition(Endpoint endpoint) {
109        from(endpoint);
110    }
111
112    /**
113     * This route is created from the REST DSL.
114     */
115    public void fromRest(@AsEndpointUri String uri) {
116        from(uri);
117        rest = true;
118    }
119
120    /**
121     * Prepares the route definition to be ready to be added to {@link CamelContext}
122     *
123     * @param context the camel context
124     */
125    public void prepare(ModelCamelContext context) {
126        if (prepared.compareAndSet(false, true)) {
127            RouteDefinitionHelper.prepareRoute(context, this);
128        }
129    }
130
131    /**
132     * Marks the route definition as prepared.
133     * <p/>
134     * This is needed if routes have been created by components such as
135     * <tt>camel-spring</tt> or <tt>camel-blueprint</tt>.
136     * Usually they share logic in the <tt>camel-core-xml</tt> module which prepares the routes.
137     */
138    public void markPrepared() {
139        prepared.set(true);
140    }
141
142    /**
143     * Marks the route definition as un-prepared.
144     * <p/>
145     * This is needed if routes have been created by components such as
146     * <tt>camel-scala</tt>. To unset the prepare so the routes can be prepared
147     * at a later stage when scala has build the routes completely.
148     */
149    public void markUnprepared() {
150        prepared.set(false);
151    }
152
153    @Override
154    public String toString() {
155        if (getId() != null) {
156            return "Route(" + getId() + ")[" + inputs + " -> " + outputs + "]";
157        } else {
158            return "Route[" + inputs + " -> " + outputs + "]";
159        }
160    }
161
162    /**
163     * Returns the status of the route if it has been registered with a {@link CamelContext}
164     */
165    public ServiceStatus getStatus(CamelContext camelContext) {
166        if (camelContext != null) {
167            ServiceStatus answer = camelContext.getRouteStatus(this.getId());
168            if (answer == null) {
169                answer = ServiceStatus.Stopped;
170            }
171            return answer;
172        }
173        return null;
174    }
175
176    public boolean isStartable(CamelContext camelContext) {
177        ServiceStatus status = getStatus(camelContext);
178        if (status == null) {
179            return true;
180        } else {
181            return status.isStartable();
182        }
183    }
184
185    public boolean isStoppable(CamelContext camelContext) {
186        ServiceStatus status = getStatus(camelContext);
187        if (status == null) {
188            return false;
189        } else {
190            return status.isStoppable();
191        }
192    }
193
194    public List<RouteContext> addRoutes(ModelCamelContext camelContext, Collection<Route> routes) throws Exception {
195        List<RouteContext> answer = new ArrayList<>();
196
197        @SuppressWarnings("deprecation")
198        ErrorHandlerFactory handler = camelContext.getErrorHandlerBuilder();
199        if (handler != null) {
200            setErrorHandlerBuilderIfNull(handler);
201        }
202
203        for (FromDefinition fromType : inputs) {
204            RouteContext routeContext;
205            try {
206                routeContext = addRoutes(camelContext, routes, fromType);
207            } catch (FailedToCreateRouteException e) {
208                throw e;
209            } catch (Exception e) {
210                // wrap in exception which provide more details about which route was failing
211                throw new FailedToCreateRouteException(getId(), toString(), e);
212            }
213            answer.add(routeContext);
214        }
215        return answer;
216    }
217
218
219    public Endpoint resolveEndpoint(CamelContext camelContext, String uri) throws NoSuchEndpointException {
220        ObjectHelper.notNull(camelContext, "CamelContext");
221        return CamelContextHelper.getMandatoryEndpoint(camelContext, uri);
222    }
223
224    public RouteDefinition adviceWith(CamelContext camelContext, RouteBuilder builder) throws Exception {
225        return adviceWith((ModelCamelContext)camelContext, builder);
226    }
227
228    /**
229     * Advices this route with the route builder.
230     * <p/>
231     * <b>Important:</b> It is recommended to only advice a given route once (you can of course advice multiple routes).
232     * If you do it multiple times, then it may not work as expected, especially when any kind of error handling is involved.
233     * The Camel team plan for Camel 3.0 to support this as internal refactorings in the routing engine is needed to support this properly.
234     * <p/>
235     * You can use a regular {@link RouteBuilder} but the specialized {@link org.apache.camel.builder.AdviceWithRouteBuilder}
236     * has additional features when using the <a href="http://camel.apache.org/advicewith.html">advice with</a> feature.
237     * We therefore suggest you to use the {@link org.apache.camel.builder.AdviceWithRouteBuilder}.
238     * <p/>
239     * The advice process will add the interceptors, on exceptions, on completions etc. configured
240     * from the route builder to this route.
241     * <p/>
242     * This is mostly used for testing purpose to add interceptors and the likes to an existing route.
243     * <p/>
244     * Will stop and remove the old route from camel context and add and start this new advised route.
245     *
246     * @param camelContext the camel context
247     * @param builder      the route builder
248     * @return a new route which is this route merged with the route builder
249     * @throws Exception can be thrown from the route builder
250     * @see AdviceWithRouteBuilder
251     */
252    @SuppressWarnings("deprecation")
253    public RouteDefinition adviceWith(ModelCamelContext camelContext, RouteBuilder builder) throws Exception {
254        ObjectHelper.notNull(camelContext, "CamelContext");
255        ObjectHelper.notNull(builder, "RouteBuilder");
256
257        log.debug("AdviceWith route before: {}", this);
258
259        // inject this route into the advice route builder so it can access this route
260        // and offer features to manipulate the route directly
261        if (builder instanceof AdviceWithRouteBuilder) {
262            ((AdviceWithRouteBuilder) builder).setOriginalRoute(this);
263        }
264
265        // configure and prepare the routes from the builder
266        RoutesDefinition routes = builder.configureRoutes(camelContext);
267
268        log.debug("AdviceWith routes: {}", routes);
269
270        // we can only advice with a route builder without any routes
271        if (!builder.getRouteCollection().getRoutes().isEmpty()) {
272            throw new IllegalArgumentException("You can only advice from a RouteBuilder which has no existing routes."
273                    + " Remove all routes from the route builder.");
274        }
275        // we can not advice with error handlers (if you added a new error handler in the route builder)
276        // we must check the error handler on builder is not the same as on camel context, as that would be the default
277        // context scoped error handler, in case no error handlers was configured
278        if (builder.getRouteCollection().getErrorHandlerBuilder() != null
279                && camelContext.getErrorHandlerBuilder() != builder.getRouteCollection().getErrorHandlerBuilder()) {
280            throw new IllegalArgumentException("You can not advice with error handlers. Remove the error handlers from the route builder.");
281        }
282
283        String beforeAsXml = ModelHelper.dumpModelAsXml(camelContext, this);
284
285        // stop and remove this existing route
286        camelContext.removeRouteDefinition(this);
287
288        // any advice with tasks we should execute first?
289        if (builder instanceof AdviceWithRouteBuilder) {
290            List<AdviceWithTask> tasks = ((AdviceWithRouteBuilder) builder).getAdviceWithTasks();
291            for (AdviceWithTask task : tasks) {
292                task.task();
293            }
294        }
295
296        // now merge which also ensures that interceptors and the likes get mixed in correctly as well
297        RouteDefinition merged = routes.route(this);
298
299        // add the new merged route
300        camelContext.getRouteDefinitions().add(0, merged);
301
302        // log the merged route at info level to make it easier to end users to spot any mistakes they may have made
303        log.info("AdviceWith route after: {}", merged);
304
305        String afterAsXml = ModelHelper.dumpModelAsXml(camelContext, merged);
306        log.info("Adviced route before/after as XML:\n{}\n{}", beforeAsXml, afterAsXml);
307
308        // If the camel context is started then we start the route
309        if (camelContext instanceof StatefulService) {
310            StatefulService service = (StatefulService) camelContext;
311            if (service.isStarted()) {
312                camelContext.startRoute(merged);
313            }
314        }
315        return merged;
316    }
317
318    // Fluent API
319    // -----------------------------------------------------------------------
320
321    /**
322     * Creates an input to the route
323     *
324     * @param uri the from uri
325     * @return the builder
326     */
327    public RouteDefinition from(@AsEndpointUri String uri) {
328        getInputs().add(new FromDefinition(uri));
329        return this;
330    }
331
332    /**
333     * Creates an input to the route
334     *
335     * @param endpoint the from endpoint
336     * @return the builder
337     */
338    public RouteDefinition from(Endpoint endpoint) {
339        getInputs().add(new FromDefinition(endpoint));
340        return this;
341    }
342
343    /**
344     * Creates inputs to the route
345     *
346     * @param uris the from uris
347     * @return the builder
348     */
349    public RouteDefinition from(@AsEndpointUri String... uris) {
350        for (String uri : uris) {
351            getInputs().add(new FromDefinition(uri));
352        }
353        return this;
354    }
355
356    /**
357     * Creates inputs to the route
358     *
359     * @param endpoints the from endpoints
360     * @return the builder
361     */
362    public RouteDefinition from(Endpoint... endpoints) {
363        for (Endpoint endpoint : endpoints) {
364            getInputs().add(new FromDefinition(endpoint));
365        }
366        return this;
367    }
368
369    /**
370     * Set the group name for this route
371     *
372     * @param name the group name
373     * @return the builder
374     */
375    public RouteDefinition group(String name) {
376        setGroup(name);
377        return this;
378    }
379
380    /**
381     * Set the route group for this route
382     *
383     * @param group the route group
384     * @return the builder
385     */
386    public RouteDefinition routeGroup(String group) {
387        setGroup(group);
388        return this;
389    }
390
391    /**
392     * Set the route id for this route
393     *
394     * @param id the route id
395     * @return the builder
396     */
397    public RouteDefinition routeId(String id) {
398        setId(id);
399        return this;
400    }
401
402    /**
403     * Set the route description for this route
404     *
405     * @param description the route description
406     * @return the builder
407     */
408    public RouteDefinition routeDescription(String description) {
409        DescriptionDefinition desc = new DescriptionDefinition();
410        desc.setText(description);
411        setDescription(desc);
412        return this;
413    }
414
415    /**
416     * Disable stream caching for this route.
417     *
418     * @return the builder
419     */
420    public RouteDefinition noStreamCaching() {
421        setStreamCache("false");
422        return this;
423    }
424
425    /**
426     * Enable stream caching for this route.
427     *
428     * @return the builder
429     */
430    public RouteDefinition streamCaching() {
431        setStreamCache("true");
432        return this;
433    }
434
435    /**
436     * Enable stream caching for this route.
437     *
438     * @param streamCache whether to use stream caching (true or false), the value can be a property placeholder
439     * @return the builder
440     */
441    public RouteDefinition streamCaching(String streamCache) {
442        setStreamCache(streamCache);
443        return this;
444    }
445
446    /**
447     * Disable tracing for this route.
448     *
449     * @return the builder
450     */
451    public RouteDefinition noTracing() {
452        setTrace("false");
453        return this;
454    }
455
456    /**
457     * Enable tracing for this route.
458     *
459     * @return the builder
460     */
461    public RouteDefinition tracing() {
462        setTrace("true");
463        return this;
464    }
465
466    /**
467     * Enable tracing for this route.
468     *
469     * @param tracing whether to use tracing (true or false), the value can be a property placeholder
470     * @return the builder
471     */
472    public RouteDefinition tracing(String tracing) {
473        setTrace(tracing);
474        return this;
475    }
476
477    /**
478     * Enable message history for this route.
479     *
480     * @return the builder
481     */
482    public RouteDefinition messageHistory() {
483        setMessageHistory("true");
484        return this;
485    }
486
487    /**
488     * Enable message history for this route.
489     *
490     * @param messageHistory whether to use message history (true or false), the value can be a property placeholder
491     * @return the builder
492     */
493    public RouteDefinition messageHistory(String messageHistory) {
494        setMessageHistory(messageHistory);
495        return this;
496    }
497
498    /**
499     * Enable security mask for Logging on this route.
500     *
501     * @return the builder
502     */
503    public RouteDefinition logMask() {
504        setLogMask("true");
505        return this;
506    }
507
508    /**
509     * Sets whether security mask for logging is enabled on this route.
510     *
511     * @param logMask whether to enable security mask for Logging (true or false), the value can be a property placeholder
512     * @return the builder
513     */
514    public RouteDefinition logMask(String logMask) {
515        setLogMask(logMask);
516        return this;
517    }
518
519    /**
520     * Disable message history for this route.
521     *
522     * @return the builder
523     */
524    public RouteDefinition noMessageHistory() {
525        setMessageHistory("false");
526        return this;
527    }
528
529    /**
530     * Disable handle fault for this route.
531     *
532     * @return the builder
533     */
534    public RouteDefinition noHandleFault() {
535        setHandleFault("false");
536        return this;
537    }
538
539    /**
540     * Enable handle fault for this route.
541     *
542     * @return the builder
543     */
544    public RouteDefinition handleFault() {
545        setHandleFault("true");
546        return this;
547    }
548
549    /**
550     * Disable delayer for this route.
551     *
552     * @return the builder
553     */
554    public RouteDefinition noDelayer() {
555        setDelayer("0");
556        return this;
557    }
558
559    /**
560     * Enable delayer for this route.
561     *
562     * @param delay delay in millis
563     * @return the builder
564     */
565    public RouteDefinition delayer(long delay) {
566        setDelayer("" + delay);
567        return this;
568    }
569
570    /**
571     * Installs the given <a href="http://camel.apache.org/error-handler.html">error handler</a> builder.
572     *
573     * @param errorHandlerBuilder the error handler to be used by default for all child routes
574     * @return the current builder with the error handler configured
575     */
576    public RouteDefinition errorHandler(ErrorHandlerFactory errorHandlerBuilder) {
577        setErrorHandlerBuilder(errorHandlerBuilder);
578        // we are now using a route scoped error handler
579        contextScopedErrorHandler = false;
580        return this;
581    }
582
583    /**
584     * Disables this route from being auto started when Camel starts.
585     *
586     * @return the builder
587     */
588    public RouteDefinition noAutoStartup() {
589        setAutoStartup("false");
590        return this;
591    }
592
593    /**
594     * Sets the auto startup property on this route.
595     *
596     * @param autoStartup whether to auto startup (true or false), the value can be a property placeholder
597     * @return the builder
598     */
599    public RouteDefinition autoStartup(String autoStartup) {
600        setAutoStartup(autoStartup);
601        return this;
602    }
603
604    /**
605     * Sets the auto startup property on this route.
606     *
607     * @param autoStartup - boolean indicator
608     * @return the builder
609     */
610    public RouteDefinition autoStartup(boolean autoStartup) {
611        setAutoStartup(Boolean.toString(autoStartup));
612        return this;
613    }
614
615    /**
616     * Configures the startup order for this route
617     * <p/>
618     * Camel will reorder routes and star them ordered by 0..N where 0 is the lowest number and N the highest number.
619     * Camel will stop routes in reverse order when its stopping.
620     *
621     * @param order the order represented as a number
622     * @return the builder
623     */
624    public RouteDefinition startupOrder(int order) {
625        setStartupOrder(order);
626        return this;
627    }
628
629    /**
630     * Configures route policies for this route
631     *
632     * @param policies the route policies
633     * @return the builder
634     */
635    public RouteDefinition routePolicy(RoutePolicy... policies) {
636        if (routePolicies == null) {
637            routePolicies = new ArrayList<>();
638        }
639        for (RoutePolicy policy : policies) {
640            routePolicies.add(policy);
641        }
642        return this;
643    }
644
645    /**
646     * Configures a route policy for this route
647     *
648     * @param routePolicyRef reference to a {@link RoutePolicy} to lookup and use.
649     *                       You can specify multiple references by separating using comma.
650     * @return the builder
651     */
652    public RouteDefinition routePolicyRef(String routePolicyRef) {
653        setRoutePolicyRef(routePolicyRef);
654        return this;
655    }
656
657    /**
658     * Configures a shutdown route option.
659     *
660     * @param shutdownRoute the option to use when shutting down this route
661     * @return the builder
662     */
663    public RouteDefinition shutdownRoute(ShutdownRoute shutdownRoute) {
664        setShutdownRoute(shutdownRoute);
665        return this;
666    }
667
668    /**
669     * Configures a shutdown running task option.
670     *
671     * @param shutdownRunningTask the option to use when shutting down and how to act upon running tasks.
672     * @return the builder
673     */
674    public RouteDefinition shutdownRunningTask(ShutdownRunningTask shutdownRunningTask) {
675        setShutdownRunningTask(shutdownRunningTask);
676        return this;
677    }
678
679    /**
680     * Declare the expected data type of the input message. If the actual message type is different
681     * at runtime, camel look for a required {@link Transformer} and apply if exists.
682     * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name'
683     * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}.
684     *
685     * @see org.apache.camel.spi.Transformer
686     *
687     * @param urn input type URN
688     * @return the builder
689     */
690    public RouteDefinition inputType(String urn) {
691        inputType = new InputTypeDefinition();
692        inputType.setUrn(urn);
693        inputType.setValidate(false);
694        return this;
695    }
696
697    /**
698     * Declare the expected data type of the input message with content validation enabled.
699     * If the actual message type is different at runtime, camel look for a required
700     * {@link Transformer} and apply if exists, and then applies {@link Validator} as well.
701     * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name'
702     * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}.
703     *
704     * @see org.apache.camel.spi.Transformer
705     * @see org.apache.camel.spi.Validator
706     *
707     * @param urn input type URN
708     * @return the builder
709     */
710    public RouteDefinition inputTypeWithValidate(String urn) {
711        inputType = new InputTypeDefinition();
712        inputType.setUrn(urn);
713        inputType.setValidate(true);
714        return this;
715    }
716
717    /**
718     * Declare the expected data type of the input message by Java class.
719     * If the actual message type is different at runtime, camel look for a required
720     * {@link Transformer} and apply if exists.
721     *
722     * @see org.apache.camel.spi.Transformer
723     *
724     * @param clazz Class object of the input type
725     * @return the builder
726     */
727    public RouteDefinition inputType(Class clazz) {
728        inputType = new InputTypeDefinition();
729        inputType.setJavaClass(clazz);
730        inputType.setValidate(false);
731        return this;
732    }
733
734    /**
735     * Declare the expected data type of the input message by Java class with content validation enabled.
736     * If the actual message type is different at runtime, camel look for a required
737     * {@link Transformer} and apply if exists, and then applies {@link Validator} as well.
738     *
739     * @see org.apache.camel.spi.Transformer
740     * @see org.apache.camel.spi.Validator
741     *
742     * @param clazz Class object of the input type
743     * @return the builder
744     */
745    public RouteDefinition inputTypeWithValidate(Class clazz) {
746        inputType = new InputTypeDefinition();
747        inputType.setJavaClass(clazz);
748        inputType.setValidate(true);
749        return this;
750    }
751
752    /**
753     * Declare the expected data type of the output message. If the actual message type is different
754     * at runtime, camel look for a required {@link Transformer} and apply if exists.
755     * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name'
756     * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}.
757     *
758     * @see org.apache.camel.spi.Transformer
759     *
760     * @param urn output type URN
761     * @return the builder
762     */
763    public RouteDefinition outputType(String urn) {
764        outputType = new OutputTypeDefinition();
765        outputType.setUrn(urn);
766        outputType.setValidate(false);
767        return this;
768    }
769
770    /**
771     * Declare the expected data type of the output message with content validation enabled.
772     * If the actual message type is different at runtime, Camel look for a required
773     * {@link Transformer} and apply if exists, and then applies {@link Validator} as well.
774     * The type name consists of two parts, 'scheme' and 'name' connected with ':'. For Java type 'name'
775     * is a fully qualified class name. For example {@code java:java.lang.String}, {@code json:ABCOrder}.
776     * 
777     * @see org.apache.camel.spi.Transformer
778     * @see org.apache.camel.spi.Validator
779     *
780     * @param urn output type URN
781     * @return the builder
782     */
783    public RouteDefinition outputTypeWithValidate(String urn) {
784        outputType = new OutputTypeDefinition();
785        outputType.setUrn(urn);
786        outputType.setValidate(true);
787        return this;
788    }
789
790    /**
791     * Declare the expected data type of the output message by Java class.
792     * If the actual message type is different at runtime, camel look for a required
793     * {@link Transformer} and apply if exists.
794     *
795     * @see org.apache.camel.spi.Transformer
796     *
797     * @param clazz Class object of the output type
798     * @return the builder
799     */
800    public RouteDefinition outputType(Class clazz) {
801        outputType = new OutputTypeDefinition();
802        outputType.setJavaClass(clazz);
803        outputType.setValidate(false);
804        return this;
805    }
806
807    /**
808     * Declare the expected data type of the ouput message by Java class with content validation enabled.
809     * If the actual message type is different at runtime, camel look for a required
810     * {@link Transformer} and apply if exists, and then applies {@link Validator} as well.
811     * 
812     * @see org.apache.camel.spi.Transformer
813     * @see org.apache.camel.spi.Validator
814     * @param clazz Class object of the output type
815     * @return the builder
816     */
817    public RouteDefinition outputTypeWithValidate(Class clazz) {
818        outputType = new OutputTypeDefinition();
819        outputType.setJavaClass(clazz);
820        outputType.setValidate(true);
821        return this;
822    }
823
824    /**
825     * Adds a custom property on the route.
826     */
827    public RouteDefinition routeProperty(String key, String value) {
828        if (routeProperties == null) {
829            routeProperties = new ArrayList<>();
830        }
831
832        PropertyDefinition prop = new PropertyDefinition();
833        prop.setKey(key);
834        prop.setValue(value);
835
836        routeProperties.add(prop);
837
838        return this;
839    }
840
841    // Properties
842    // -----------------------------------------------------------------------
843
844    public List<FromDefinition> getInputs() {
845        return inputs;
846    }
847
848    /**
849     * Input to the route.
850     */
851    @XmlElementRef
852    public void setInputs(List<FromDefinition> inputs) {
853        this.inputs = inputs;
854    }
855
856    public List<ProcessorDefinition<?>> getOutputs() {
857        return outputs;
858    }
859
860    /**
861     * Outputs are processors that determines how messages are processed by this route.
862     */
863    @XmlElementRef
864    public void setOutputs(List<ProcessorDefinition<?>> outputs) {
865        this.outputs = outputs;
866
867        if (outputs != null) {
868            for (ProcessorDefinition<?> output : outputs) {
869                configureChild(output);
870            }
871        }
872    }
873
874    public boolean isOutputSupported() {
875        return true;
876    }
877
878    /**
879     * The group that this route belongs to; could be the name of the RouteBuilder class
880     * or be explicitly configured in the XML.
881     * <p/>
882     * May be null.
883     */
884    public String getGroup() {
885        return group;
886    }
887
888    /**
889     * The group that this route belongs to; could be the name of the RouteBuilder class
890     * or be explicitly configured in the XML.
891     * <p/>
892     * May be null.
893     */
894    @XmlAttribute
895    public void setGroup(String group) {
896        this.group = group;
897    }
898
899    /**
900     * Whether stream caching is enabled on this route.
901     */
902    public String getStreamCache() {
903        return streamCache;
904    }
905
906    /**
907     * Whether stream caching is enabled on this route.
908     */
909    @XmlAttribute
910    public void setStreamCache(String streamCache) {
911        this.streamCache = streamCache;
912    }
913
914    /**
915     * Whether tracing is enabled on this route.
916     */
917    public String getTrace() {
918        return trace;
919    }
920
921    /**
922     * Whether tracing is enabled on this route.
923     */
924    @XmlAttribute
925    public void setTrace(String trace) {
926        this.trace = trace;
927    }
928
929    /**
930     * Whether message history is enabled on this route.
931     */
932    public String getMessageHistory() {
933        return messageHistory;
934    }
935
936    /**
937     * Whether message history is enabled on this route.
938     */
939    @XmlAttribute @Metadata(defaultValue = "true")
940    public void setMessageHistory(String messageHistory) {
941        this.messageHistory = messageHistory;
942    }
943
944    /**
945     * Whether security mask for Logging is enabled on this route.
946     */
947    public String getLogMask() {
948        return logMask;
949    }
950
951    /**
952     * Whether security mask for Logging is enabled on this route.
953     */
954    @XmlAttribute
955    public void setLogMask(String logMask) {
956        this.logMask = logMask;
957    }
958
959    /**
960     * Whether handle fault is enabled on this route.
961     */
962    public String getHandleFault() {
963        return handleFault;
964    }
965
966    /**
967     * Whether handle fault is enabled on this route.
968     */
969    @XmlAttribute
970    public void setHandleFault(String handleFault) {
971        this.handleFault = handleFault;
972    }
973
974    /**
975     * Whether to slow down processing messages by a given delay in msec.
976     */
977    public String getDelayer() {
978        return delayer;
979    }
980
981    /**
982     * Whether to slow down processing messages by a given delay in msec.
983     */
984    @XmlAttribute
985    public void setDelayer(String delayer) {
986        this.delayer = delayer;
987    }
988
989    /**
990     * Whether to auto start this route
991     */
992    public String getAutoStartup() {
993        return autoStartup;
994    }
995
996    public boolean isAutoStartup(CamelContext camelContext) throws Exception {
997        if (getAutoStartup() == null) {
998            // should auto startup by default
999            return true;
1000        }
1001        Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, getAutoStartup());
1002        return isAutoStartup != null && isAutoStartup;
1003    }
1004
1005    /**
1006     * Whether to auto start this route
1007     */
1008    @XmlAttribute @Metadata(defaultValue = "true")
1009    public void setAutoStartup(String autoStartup) {
1010        this.autoStartup = autoStartup;
1011    }
1012
1013    /**
1014     * To configure the ordering of the routes being started
1015     */
1016    public Integer getStartupOrder() {
1017        return startupOrder;
1018    }
1019
1020    /**
1021     * To configure the ordering of the routes being started
1022     */
1023    @XmlAttribute
1024    public void setStartupOrder(Integer startupOrder) {
1025        this.startupOrder = startupOrder;
1026    }
1027
1028    /**
1029     * Sets the bean ref name of the error handler builder to use on this route
1030     */
1031    @XmlAttribute
1032    public void setErrorHandlerRef(String errorHandlerRef) {
1033        this.errorHandlerRef = errorHandlerRef;
1034        // we use an specific error handler ref (from Spring DSL) then wrap that
1035        // with a error handler build ref so Camel knows its not just the default one
1036        setErrorHandlerBuilder(new ErrorHandlerBuilderRef(errorHandlerRef));
1037    }
1038
1039    /**
1040     * Sets the bean ref name of the error handler builder to use on this route
1041     */
1042    public String getErrorHandlerRef() {
1043        return errorHandlerRef;
1044    }
1045
1046    /**
1047     * Sets the error handler if one is not already set
1048     */
1049    public void setErrorHandlerBuilderIfNull(ErrorHandlerFactory errorHandlerBuilder) {
1050        if (this.errorHandlerBuilder == null) {
1051            setErrorHandlerBuilder(errorHandlerBuilder);
1052        }
1053    }
1054
1055    /**
1056     * Reference to custom {@link org.apache.camel.spi.RoutePolicy} to use by the route.
1057     * Multiple policies can be configured by separating values using comma.
1058     */
1059    @XmlAttribute
1060    public void setRoutePolicyRef(String routePolicyRef) {
1061        this.routePolicyRef = routePolicyRef;
1062    }
1063
1064    /**
1065     * Reference to custom {@link org.apache.camel.spi.RoutePolicy} to use by the route.
1066     * Multiple policies can be configured by separating values using comma.
1067     */
1068    public String getRoutePolicyRef() {
1069        return routePolicyRef;
1070    }
1071
1072    public List<RoutePolicy> getRoutePolicies() {
1073        return routePolicies;
1074    }
1075
1076    @XmlTransient
1077    public void setRoutePolicies(List<RoutePolicy> routePolicies) {
1078        this.routePolicies = routePolicies;
1079    }
1080
1081    public ShutdownRoute getShutdownRoute() {
1082        return shutdownRoute;
1083    }
1084
1085    /**
1086     * To control how to shutdown the route.
1087     */
1088    @XmlAttribute @Metadata(defaultValue = "Default")
1089    public void setShutdownRoute(ShutdownRoute shutdownRoute) {
1090        this.shutdownRoute = shutdownRoute;
1091    }
1092
1093    /**
1094     * To control how to shutdown the route.
1095     */
1096    public ShutdownRunningTask getShutdownRunningTask() {
1097        return shutdownRunningTask;
1098    }
1099
1100    /**
1101     * To control how to shutdown the route.
1102     */
1103    @XmlAttribute @Metadata(defaultValue = "CompleteCurrentTaskOnly")
1104    public void setShutdownRunningTask(ShutdownRunningTask shutdownRunningTask) {
1105        this.shutdownRunningTask = shutdownRunningTask;
1106    }
1107
1108    private ErrorHandlerFactory createErrorHandlerBuilder() {
1109        if (errorHandlerRef != null) {
1110            return new ErrorHandlerBuilderRef(errorHandlerRef);
1111        }
1112
1113        // return a reference to the default error handler
1114        return new ErrorHandlerBuilderRef(ErrorHandlerBuilderRef.DEFAULT_ERROR_HANDLER_BUILDER);
1115    }
1116
1117    @XmlTransient
1118    public ErrorHandlerFactory getErrorHandlerBuilder() {
1119        if (errorHandlerBuilder == null) {
1120            errorHandlerBuilder = createErrorHandlerBuilder();
1121        }
1122        return errorHandlerBuilder;
1123    }
1124
1125    /**
1126     * Sets the error handler to use with processors created by this builder
1127     */
1128    public void setErrorHandlerBuilder(ErrorHandlerFactory errorHandlerBuilder) {
1129        this.errorHandlerBuilder = errorHandlerBuilder;
1130    }
1131
1132    @XmlAttribute
1133    public Boolean isRest() {
1134        return rest;
1135    }
1136
1137    public RestDefinition getRestDefinition() {
1138        return restDefinition;
1139    }
1140
1141    @XmlTransient
1142    public void setRestDefinition(RestDefinition restDefinition) {
1143        this.restDefinition = restDefinition;
1144    }
1145
1146    public RestBindingDefinition getRestBindingDefinition() {
1147        return restBindingDefinition;
1148    }
1149
1150    @XmlTransient
1151    public void setRestBindingDefinition(RestBindingDefinition restBindingDefinition) {
1152        this.restBindingDefinition = restBindingDefinition;
1153    }
1154
1155    @SuppressWarnings("deprecation")
1156    public boolean isContextScopedErrorHandler(CamelContext context) {
1157        if (!contextScopedErrorHandler) {
1158            return false;
1159        }
1160        // if error handler ref is configured it may refer to a context scoped, so we need to check this first
1161        // the XML DSL will configure error handlers using refs, so we need this additional test
1162        if (errorHandlerRef != null) {
1163            ErrorHandlerFactory routeScoped = getErrorHandlerBuilder();
1164            ErrorHandlerFactory contextScoped = context.getErrorHandlerBuilder();
1165            return routeScoped != null && contextScoped != null && routeScoped == contextScoped;
1166        }
1167
1168        return true;
1169    }
1170
1171    @XmlElementRef(required = false)
1172    public void setInputType(InputTypeDefinition inputType) {
1173        this.inputType = inputType;
1174    }
1175
1176    public InputTypeDefinition getInputType() {
1177        return this.inputType;
1178    }
1179
1180    @XmlElementRef(required = false)
1181    public void setOutputType(OutputTypeDefinition outputType) {
1182        this.outputType = outputType;
1183    }
1184
1185    public OutputTypeDefinition getOutputType() {
1186        return this.outputType;
1187    }
1188
1189    public List<PropertyDefinition> getRouteProperties() {
1190        return routeProperties;
1191    }
1192
1193    /**
1194     * To set metadata as properties on the route.
1195     */
1196    @XmlElement(name = "routeProperty")
1197    @Metadata(label = "advanced")
1198    public void setRouteProperties(List<PropertyDefinition> routeProperties) {
1199        this.routeProperties = routeProperties;
1200    }
1201
1202    // Implementation methods
1203    // -------------------------------------------------------------------------
1204    protected RouteContext addRoutes(CamelContext camelContext, Collection<Route> routes, FromDefinition fromType) throws Exception {
1205        RouteContext routeContext = new DefaultRouteContext(camelContext, this, fromType, routes);
1206
1207        // configure tracing
1208        if (trace != null) {
1209            Boolean isTrace = CamelContextHelper.parseBoolean(camelContext, getTrace());
1210            if (isTrace != null) {
1211                routeContext.setTracing(isTrace);
1212                if (isTrace) {
1213                    log.debug("Tracing is enabled on route: {}", getId());
1214                    // tracing is added in the DefaultChannel so we can enable it on the fly
1215                }
1216            }
1217        }
1218
1219        // configure message history
1220        if (messageHistory != null) {
1221            Boolean isMessageHistory = CamelContextHelper.parseBoolean(camelContext, getMessageHistory());
1222            if (isMessageHistory != null) {
1223                routeContext.setMessageHistory(isMessageHistory);
1224                if (isMessageHistory) {
1225                    log.debug("Message history is enabled on route: {}", getId());
1226                }
1227            }
1228        }
1229
1230        // configure Log EIP mask
1231        if (logMask != null) {
1232            Boolean isLogMask = CamelContextHelper.parseBoolean(camelContext, getLogMask());
1233            if (isLogMask != null) {
1234                routeContext.setLogMask(isLogMask);
1235                if (isLogMask) {
1236                    log.debug("Security mask for Logging is enabled on route: {}", getId());
1237                }
1238            }
1239        }
1240
1241        // configure stream caching
1242        if (streamCache != null) {
1243            Boolean isStreamCache = CamelContextHelper.parseBoolean(camelContext, getStreamCache());
1244            if (isStreamCache != null) {
1245                routeContext.setStreamCaching(isStreamCache);
1246                if (isStreamCache) {
1247                    log.debug("StreamCaching is enabled on route: {}", getId());
1248                }
1249            }
1250        }
1251
1252        // configure handle fault
1253        if (handleFault != null) {
1254            Boolean isHandleFault = CamelContextHelper.parseBoolean(camelContext, getHandleFault());
1255            if (isHandleFault != null) {
1256                routeContext.setHandleFault(isHandleFault);
1257                if (isHandleFault) {
1258                    log.debug("HandleFault is enabled on route: {}", getId());
1259                    // only add a new handle fault if not already a global configured on camel context
1260                    if (HandleFault.getHandleFault(camelContext) == null) {
1261                        addInterceptStrategy(new HandleFault());
1262                    }
1263                }
1264            }
1265        }
1266
1267        // configure delayer
1268        if (delayer != null) {
1269            Long delayer = CamelContextHelper.parseLong(camelContext, getDelayer());
1270            if (delayer != null) {
1271                routeContext.setDelayer(delayer);
1272                if (delayer > 0) {
1273                    log.debug("Delayer is enabled with: {} ms. on route: {}", delayer, getId());
1274                } else {
1275                    log.debug("Delayer is disabled on route: {}", getId());
1276                }
1277            }
1278        }
1279
1280        // configure route policy
1281        if (routePolicies != null && !routePolicies.isEmpty()) {
1282            for (RoutePolicy policy : routePolicies) {
1283                log.debug("RoutePolicy is enabled: {} on route: {}", policy, getId());
1284                routeContext.getRoutePolicyList().add(policy);
1285            }
1286        }
1287        if (routePolicyRef != null) {
1288            StringTokenizer policyTokens = new StringTokenizer(routePolicyRef, ",");
1289            while (policyTokens.hasMoreTokens()) {
1290                String ref = policyTokens.nextToken().trim();
1291                RoutePolicy policy = CamelContextHelper.mandatoryLookup(camelContext, ref, RoutePolicy.class);
1292                log.debug("RoutePolicy is enabled: {} on route: {}", policy, getId());
1293                routeContext.getRoutePolicyList().add(policy);
1294            }
1295        }
1296        if (camelContext.getRoutePolicyFactories() != null) {
1297            for (RoutePolicyFactory factory : camelContext.getRoutePolicyFactories()) {
1298                RoutePolicy policy = factory.createRoutePolicy(camelContext, getId(), this);
1299                if (policy != null) {
1300                    log.debug("RoutePolicy is enabled: {} on route: {}", policy, getId());
1301                    routeContext.getRoutePolicyList().add(policy);
1302                }
1303            }
1304        }
1305
1306        // configure auto startup
1307        Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, getAutoStartup());
1308        if (isAutoStartup != null) {
1309            log.debug("Using AutoStartup {} on route: {}", isAutoStartup, getId());
1310            routeContext.setAutoStartup(isAutoStartup);
1311        }
1312
1313        // configure shutdown
1314        if (shutdownRoute != null) {
1315            log.debug("Using ShutdownRoute {} on route: {}", getShutdownRoute(), getId());
1316            routeContext.setShutdownRoute(getShutdownRoute());
1317        }
1318        if (shutdownRunningTask != null) {
1319            log.debug("Using ShutdownRunningTask {} on route: {}", getShutdownRunningTask(), getId());
1320            routeContext.setShutdownRunningTask(getShutdownRunningTask());
1321        }
1322
1323        // should inherit the intercept strategies we have defined
1324        routeContext.setInterceptStrategies(this.getInterceptStrategies());
1325        // force endpoint resolution
1326        routeContext.getEndpoint();
1327        for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
1328            strategy.onRouteContextCreate(routeContext);
1329        }
1330
1331        // validate route has output processors
1332        if (!ProcessorDefinitionHelper.hasOutputs(outputs, true)) {
1333            RouteDefinition route = routeContext.getRoute();
1334            String at = fromType.toString();
1335            Exception cause = new IllegalArgumentException("Route " + route.getId() + " has no output processors."
1336                    + " You need to add outputs to the route such as to(\"log:foo\").");
1337            throw new FailedToCreateRouteException(route.getId(), route.toString(), at, cause);
1338        }
1339
1340        List<ProcessorDefinition<?>> list = new ArrayList<>(outputs);
1341        for (ProcessorDefinition<?> output : list) {
1342            try {
1343                output.addRoutes(routeContext, routes);
1344            } catch (Exception e) {
1345                RouteDefinition route = routeContext.getRoute();
1346                throw new FailedToCreateRouteException(route.getId(), route.toString(), output.toString(), e);
1347            }
1348        }
1349
1350        routeContext.commit();
1351        return routeContext;
1352    }
1353
1354
1355    // ****************************
1356    // Static helpers
1357    // ****************************
1358
1359    public static RouteDefinition fromUri(String uri) {
1360        return new RouteDefinition().from(uri);
1361    }
1362
1363    public static RouteDefinition fromEndpoint(Endpoint endpoint) {
1364        return new RouteDefinition().from(endpoint);
1365    }
1366
1367}