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