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     */
017    package org.apache.camel.model;
018    
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.List;
022    import java.util.concurrent.atomic.AtomicBoolean;
023    import javax.xml.bind.annotation.XmlAccessType;
024    import javax.xml.bind.annotation.XmlAccessorType;
025    import javax.xml.bind.annotation.XmlAttribute;
026    import javax.xml.bind.annotation.XmlElementRef;
027    import javax.xml.bind.annotation.XmlRootElement;
028    import javax.xml.bind.annotation.XmlTransient;
029    import javax.xml.bind.annotation.XmlType;
030    
031    import org.apache.camel.CamelContext;
032    import org.apache.camel.Endpoint;
033    import org.apache.camel.FailedToCreateRouteException;
034    import org.apache.camel.NoSuchEndpointException;
035    import org.apache.camel.Route;
036    import org.apache.camel.ServiceStatus;
037    import org.apache.camel.ShutdownRoute;
038    import org.apache.camel.ShutdownRunningTask;
039    import org.apache.camel.builder.ErrorHandlerBuilder;
040    import org.apache.camel.builder.ErrorHandlerBuilderRef;
041    import org.apache.camel.builder.RouteBuilder;
042    import org.apache.camel.impl.DefaultRouteContext;
043    import org.apache.camel.processor.interceptor.Delayer;
044    import org.apache.camel.processor.interceptor.HandleFault;
045    import org.apache.camel.processor.interceptor.StreamCaching;
046    import org.apache.camel.spi.LifecycleStrategy;
047    import org.apache.camel.spi.RouteContext;
048    import org.apache.camel.spi.RoutePolicy;
049    import org.apache.camel.util.CamelContextHelper;
050    import org.apache.camel.util.ObjectHelper;
051    
052    /**
053     * Represents an XML <route/> element
054     *
055     * @version $Revision: 1021867 $
056     */
057    @XmlRootElement(name = "route")
058    @XmlType(propOrder = {"inputs", "outputs" })
059    @XmlAccessorType(XmlAccessType.PROPERTY)
060    public class RouteDefinition extends ProcessorDefinition<RouteDefinition> {
061        private final AtomicBoolean prepared = new AtomicBoolean(false);
062        private List<FromDefinition> inputs = new ArrayList<FromDefinition>();
063        private List<ProcessorDefinition> outputs = new ArrayList<ProcessorDefinition>();
064        private String group;
065        private String streamCache;
066        private String trace;
067        private String handleFault;
068        private String delayer;
069        private String autoStartup;
070        private Integer startupOrder;
071        private RoutePolicy routePolicy;
072        private String routePolicyRef;
073        private ShutdownRoute shutdownRoute;
074        private ShutdownRunningTask shutdownRunningTask;
075    
076        public RouteDefinition() {
077        }
078    
079        public RouteDefinition(String uri) {
080            from(uri);
081        }
082    
083        public RouteDefinition(Endpoint endpoint) {
084            from(endpoint);
085        }
086    
087        /**
088         * Prepares the route definition to be ready to be added to {@link CamelContext}
089         */
090        public void prepare() {
091            if (prepared.compareAndSet(false, true)) {
092                // at first init the parent
093                RouteDefinitionHelper.initParent(this);
094    
095                // abstracts is the cross cutting concerns
096                List<ProcessorDefinition> abstracts = new ArrayList<ProcessorDefinition>();
097    
098                // upper is the cross cutting concerns such as interceptors, error handlers etc
099                List<ProcessorDefinition> upper = new ArrayList<ProcessorDefinition>();
100    
101                // lower is the regular route
102                List<ProcessorDefinition> lower = new ArrayList<ProcessorDefinition>();
103    
104                RouteDefinitionHelper.prepareRouteForInit(this, abstracts, lower);
105    
106                // rebuild route as upper + lower
107                this.clearOutput();
108                this.getOutputs().addAll(lower);
109                this.getOutputs().addAll(0, upper);
110            }
111        }
112    
113        /**
114         * Marks the route definition as already prepared, for example using custom logic
115         * such as a {@link RouteBuilder} or from <tt>camel-core-xml</tt> component.
116         */
117        public void customPrepared() {
118            prepared.set(true);
119        }
120    
121        @Override
122        public String toString() {
123            return "Route[" + inputs + " -> " + outputs + "]";
124        }
125    
126        @Override
127        public String getShortName() {
128            return "route";
129        }
130    
131        /**
132         * Returns the status of the route if it has been registered with a {@link CamelContext}
133         */
134        public ServiceStatus getStatus(CamelContext camelContext) {
135            if (camelContext != null) {
136                ServiceStatus answer = camelContext.getRouteStatus(this.getId());
137                if (answer == null) {
138                    answer = ServiceStatus.Stopped;
139                }
140                return answer;
141            }
142            return null;
143        }
144    
145        public boolean isStartable(CamelContext camelContext) {
146            ServiceStatus status = getStatus(camelContext);
147            if (status == null) {
148                return true;
149            } else {
150                return status.isStartable();
151            }
152        }
153    
154        public boolean isStoppable(CamelContext camelContext) {
155            ServiceStatus status = getStatus(camelContext);
156            if (status == null) {
157                return false;
158            } else {
159                return status.isStoppable();
160            }
161        }
162        
163        public List<RouteContext> addRoutes(CamelContext camelContext, Collection<Route> routes) throws Exception {
164            List<RouteContext> answer = new ArrayList<RouteContext>();
165    
166            ErrorHandlerBuilder handler = camelContext.getErrorHandlerBuilder();
167            if (handler != null) {
168                setErrorHandlerBuilderIfNull(handler);
169            }
170    
171            for (FromDefinition fromType : inputs) {
172                RouteContext routeContext;
173                try {
174                    routeContext = addRoutes(camelContext, routes, fromType);
175                } catch (FailedToCreateRouteException e) {
176                    throw e;
177                } catch (Exception e) {
178                    // wrap in exception which provide more details about which route was failing
179                    throw new FailedToCreateRouteException(getId(), toString(), e);
180                }
181                answer.add(routeContext);
182            }
183            return answer;
184        }
185    
186    
187        public Endpoint resolveEndpoint(CamelContext camelContext, String uri) throws NoSuchEndpointException {
188            ObjectHelper.notNull(camelContext, "CamelContext");
189            return CamelContextHelper.getMandatoryEndpoint(camelContext, uri);
190        }
191    
192        /**
193         * Advices this route with the route builder.
194         * <p/>
195         * The advice process will add the interceptors, on exceptions, on completions etc. configured
196         * from the route builder to this route.
197         * <p/>
198         * This is mostly used for testing purpose to add interceptors and the likes to an existing route.
199         * <p/>
200         * Will stop and remove the old route from camel context and add and start this new advised route.
201         *
202         * @param camelContext  the camel context
203         * @param builder       the route builder
204         * @return a new route which is this route merged with the route builder
205         * @throws Exception can be thrown from the route builder
206         */
207        public RouteDefinition adviceWith(CamelContext camelContext, RouteBuilder builder) throws Exception {
208            ObjectHelper.notNull(camelContext, "CamelContext");
209            ObjectHelper.notNull(builder, "RouteBuilder");
210    
211            // configure and prepare the routes from the builder
212            RoutesDefinition routes = builder.configureRoutes(camelContext);
213    
214            // we can only advice with a route builder without any routes
215            if (!routes.getRoutes().isEmpty()) {
216                throw new IllegalArgumentException("You can only advice from a RouteBuilder which has no existing routes."
217                        + " Remove all routes from the route builder.");
218            }
219    
220            // stop and remove this existing route
221            camelContext.removeRouteDefinition(this);
222    
223            // now merge which also ensures that interceptors and the likes get mixed in correctly as well
224            RouteDefinition merged = routes.route(this);
225    
226            // add the new merged route
227            camelContext.getRouteDefinitions().add(0, merged);
228    
229            // and start it
230            camelContext.startRoute(merged);
231            return merged;
232        }
233    
234        // Fluent API
235        // -----------------------------------------------------------------------
236    
237        /**
238         * Creates an input to the route
239         *
240         * @param uri  the from uri
241         * @return the builder
242         */
243        public RouteDefinition from(String uri) {
244            getInputs().add(new FromDefinition(uri));
245            return this;
246        }
247    
248        /**
249         * Creates an input to the route
250         *
251         * @param endpoint  the from endpoint
252         * @return the builder
253         */
254        public RouteDefinition from(Endpoint endpoint) {
255            getInputs().add(new FromDefinition(endpoint));
256            return this;
257        }
258    
259        /**
260         * Creates inputs to the route
261         *
262         * @param uris  the from uris
263         * @return the builder
264         */
265        public RouteDefinition from(String... uris) {
266            for (String uri : uris) {
267                getInputs().add(new FromDefinition(uri));
268            }
269            return this;
270        }
271    
272        /**
273         * Creates inputs to the route
274         *
275         * @param endpoints  the from endpoints
276         * @return the builder
277         */
278        public RouteDefinition from(Endpoint... endpoints) {
279            for (Endpoint endpoint : endpoints) {
280                getInputs().add(new FromDefinition(endpoint));
281            }
282            return this;
283        }
284    
285        /**
286         * Set the group name for this route
287         *
288         * @param name  the group name
289         * @return the builder
290         */
291        public RouteDefinition group(String name) {
292            setGroup(name);
293            return this;
294        }
295    
296        /**
297         * Set the route id for this route
298         *
299         * @param id  the route id
300         * @return the builder
301         */
302        public RouteDefinition routeId(String id) {
303            setId(id);
304            return this;
305        }
306    
307        /**
308         * Disable stream caching for this route.
309         * 
310         * @return the builder
311         */
312        public RouteDefinition noStreamCaching() {
313            setStreamCache("false");
314            StreamCaching.noStreamCaching(getInterceptStrategies());
315            return this;
316        }
317    
318        /**
319         * Enable stream caching for this route.
320         * 
321         * @return the builder
322         */
323        public RouteDefinition streamCaching() {
324            setStreamCache("true");
325            StreamCaching cache = StreamCaching.getStreamCaching(getInterceptStrategies());
326            if (cache == null) {
327                cache = new StreamCaching();
328            }
329    
330            getInterceptStrategies().add(cache);
331            return this;
332        }
333    
334        /**
335         * Disable tracing for this route.
336         * 
337         * @return the builder
338         */
339        public RouteDefinition noTracing() {
340            setTrace("false");
341            return this;
342        }
343    
344        /**
345         * Enable tracing for this route.
346         * 
347         * @return the builder
348         */
349        public RouteDefinition tracing() {
350            setTrace("true");
351            return this;
352        }
353    
354        /**
355         * Disable handle fault for this route.
356         * 
357         * @return the builder
358         */
359        public RouteDefinition noHandleFault() {
360            setHandleFault("false");
361            return this;
362        }
363    
364        /**
365         * Enable handle fault for this route.
366         * 
367         * @return the builder
368         */
369        public RouteDefinition handleFault() {
370            setHandleFault("true");
371            return this;
372        }
373    
374        /**
375         * Disable delayer for this route.
376         * 
377         * @return the builder
378         */
379        public RouteDefinition noDelayer() {
380            setDelayer("0");
381            return this;
382        }
383    
384        /**
385         * Enable delayer for this route.
386         *
387         * @param delay delay in millis
388         * @return the builder
389         */
390        public RouteDefinition delayer(long delay) {
391            setDelayer("" + delay);
392            return this;
393        }
394    
395        /**
396         * Installs the given <a href="http://camel.apache.org/error-handler.html">error handler</a> builder.
397         *
398         * @param errorHandlerBuilder the error handler to be used by default for all child routes
399         * @return the current builder with the error handler configured
400         */
401        public RouteDefinition errorHandler(ErrorHandlerBuilder errorHandlerBuilder) {
402            setErrorHandlerBuilder(errorHandlerBuilder);
403            return this;
404        }
405    
406        /**
407         * Disables this route from being auto started when Camel starts.
408         * 
409         * @return the builder
410         */
411        public RouteDefinition noAutoStartup() {
412            setAutoStartup("false");
413            return this;
414        }
415    
416        /**
417         * Configures the startup order for this route
418         * <p/>
419         * Camel will reorder routes and star them ordered by 0..N where 0 is the lowest number and N the highest number.
420         * Camel will stop routes in reverse order when its stopping.
421         *
422         * @param order the order represented as a number
423         * @return the builder
424         */
425        public RouteDefinition startupOrder(int order) {
426            setStartupOrder(order);
427            return this;
428        }
429    
430        /**
431         * Configures a route policy for this route
432         *
433         * @param routePolicy the route policy
434         * @return the builder
435         */ 
436        public RouteDefinition routePolicy(RoutePolicy routePolicy) {
437            setRoutePolicy(routePolicy);
438            return this;
439        }
440    
441        /**
442         * Configures a route policy for this route
443         *
444         * @param routePolicyRef reference to a {@link RoutePolicy} to lookup and use.
445         * @return the builder
446         */
447        public RouteDefinition routePolicyRef(String routePolicyRef) {
448            setRoutePolicyRef(routePolicyRef);
449            return this;
450        }
451    
452        /**
453         * Configures a shutdown route option.
454         *
455         * @param shutdownRoute the option to use when shutting down this route
456         * @return the builder
457         */
458        public RouteDefinition shutdownRoute(ShutdownRoute shutdownRoute) {
459            setShutdownRoute(shutdownRoute);
460            return this;
461        }
462    
463        /**
464         * Configures a shutdown running task option.
465         *
466         * @param shutdownRunningTask the option to use when shutting down and how to act upon running tasks.
467         * @return the builder
468         */
469        public RouteDefinition shutdownRunningTask(ShutdownRunningTask shutdownRunningTask) {
470            setShutdownRunningTask(shutdownRunningTask);
471            return this;
472        }
473    
474        // Properties
475        // -----------------------------------------------------------------------
476    
477        public List<FromDefinition> getInputs() {
478            return inputs;
479        }
480    
481        @XmlElementRef
482        public void setInputs(List<FromDefinition> inputs) {
483            this.inputs = inputs;
484        }
485    
486        public List<ProcessorDefinition> getOutputs() {
487            return outputs;
488        }
489    
490        @XmlElementRef
491        public void setOutputs(List<ProcessorDefinition> outputs) {
492            this.outputs = outputs;
493    
494            if (outputs != null) {
495                for (ProcessorDefinition output : outputs) {
496                    configureChild(output);
497                }
498            }
499        }
500    
501        /**
502         * The group that this route belongs to; could be the name of the RouteBuilder class
503         * or be explicitly configured in the XML.
504         *
505         * May be null.
506         */
507        public String getGroup() {
508            return group;
509        }
510    
511        @XmlAttribute
512        public void setGroup(String group) {
513            this.group = group;
514        }
515    
516        public String getStreamCache() {
517            return streamCache;
518        }
519    
520        @XmlAttribute
521        public void setStreamCache(String streamCache) {
522            this.streamCache = streamCache;
523        }
524    
525        public String getTrace() {
526            return trace;
527        }
528    
529        @XmlAttribute
530        public void setTrace(String trace) {
531            this.trace = trace;
532        }
533    
534        public String getHandleFault() {
535            return handleFault;
536        }
537    
538        @XmlAttribute
539        public void setHandleFault(String handleFault) {
540            this.handleFault = handleFault;
541        }
542    
543        public String getDelayer() {
544            return delayer;
545        }
546    
547        @XmlAttribute
548        public void setDelayer(String delayer) {
549            this.delayer = delayer;
550        }
551    
552        public String getAutoStartup() {
553            return autoStartup;
554        }
555    
556        public boolean isAutoStartup(CamelContext camelContext) throws Exception {
557            if (getAutoStartup() == null) {
558                // should auto startup by default
559                return true;
560            }
561            Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, getAutoStartup());
562            return isAutoStartup != null && isAutoStartup;
563        }
564    
565        @XmlAttribute
566        public void setAutoStartup(String autoStartup) {
567            this.autoStartup = autoStartup;
568        }
569    
570        public Integer getStartupOrder() {
571            return startupOrder;
572        }
573    
574        @XmlAttribute
575        public void setStartupOrder(Integer startupOrder) {
576            this.startupOrder = startupOrder;
577        }
578    
579        /**
580         * Sets the bean ref name of the error handler builder to use on this route
581         */
582        @XmlAttribute
583        public void setErrorHandlerRef(String errorHandlerRef) {
584            this.errorHandlerRef = errorHandlerRef;
585            // we use an specific error handler ref (from Spring DSL) then wrap that
586            // with a error handler build ref so Camel knows its not just the default one
587            setErrorHandlerBuilder(new ErrorHandlerBuilderRef(errorHandlerRef));
588        }
589    
590        public String getErrorHandlerRef() {
591            return errorHandlerRef;
592        }
593    
594        /**
595         * Sets the error handler if one is not already set
596         */
597        protected void setErrorHandlerBuilderIfNull(ErrorHandlerBuilder errorHandlerBuilder) {
598            if (this.errorHandlerBuilder == null) {
599                setErrorHandlerBuilder(errorHandlerBuilder);
600            }
601        }
602    
603        @XmlAttribute
604        public void setRoutePolicyRef(String routePolicyRef) {
605            this.routePolicyRef = routePolicyRef;
606        }
607    
608        public String getRoutePolicyRef() {
609            return routePolicyRef;
610        }
611    
612        @XmlTransient
613        public void setRoutePolicy(RoutePolicy routePolicy) {
614            this.routePolicy = routePolicy;
615        }
616    
617        public RoutePolicy getRoutePolicy() {
618            return routePolicy;
619        }
620    
621        public ShutdownRoute getShutdownRoute() {
622            return shutdownRoute;
623        }
624    
625        @XmlAttribute
626        public void setShutdownRoute(ShutdownRoute shutdownRoute) {
627            this.shutdownRoute = shutdownRoute;
628        }
629    
630        public ShutdownRunningTask getShutdownRunningTask() {
631            return shutdownRunningTask;
632        }
633    
634        @XmlAttribute
635        public void setShutdownRunningTask(ShutdownRunningTask shutdownRunningTask) {
636            this.shutdownRunningTask = shutdownRunningTask;
637        }
638    
639        // Implementation methods
640        // -------------------------------------------------------------------------
641        @SuppressWarnings("unchecked")
642        protected RouteContext addRoutes(CamelContext camelContext, Collection<Route> routes, FromDefinition fromType) throws Exception {
643            RouteContext routeContext = new DefaultRouteContext(camelContext, this, fromType, routes);
644    
645            // configure tracing
646            if (trace != null) {
647                Boolean isTrace = CamelContextHelper.parseBoolean(camelContext, getTrace());
648                if (isTrace != null) {
649                    routeContext.setTracing(isTrace);
650                    if (isTrace) {
651                        if (log.isDebugEnabled()) {
652                            log.debug("Tracing is enabled on route: " + this);
653                        }
654                        // tracing is added in the DefaultChannel so we can enable it on the fly
655                    }
656                }
657            }
658    
659            // configure stream caching
660            if (streamCache != null) {
661                Boolean isStreamCache = CamelContextHelper.parseBoolean(camelContext, getStreamCache());
662                if (isStreamCache != null) {
663                    routeContext.setStreamCaching(isStreamCache);
664                    if (isStreamCache) {
665                        if (log.isDebugEnabled()) {
666                            log.debug("StreamCaching is enabled on route: " + this);
667                        }
668                        // only add a new stream cache if not already a global configured on camel context
669                        if (StreamCaching.getStreamCaching(camelContext) == null) {
670                            addInterceptStrategy(new StreamCaching());
671                        }
672                    }
673                }
674            }
675    
676            // configure handle fault
677            if (handleFault != null) {
678                Boolean isHandleFault = CamelContextHelper.parseBoolean(camelContext, getHandleFault());
679                if (isHandleFault != null) {
680                    routeContext.setHandleFault(isHandleFault);
681                    if (isHandleFault) {
682                        if (log.isDebugEnabled()) {
683                            log.debug("HandleFault is enabled on route: " + this);
684                        }
685                        // only add a new handle fault if not already a global configured on camel context
686                        if (HandleFault.getHandleFault(camelContext) == null) {
687                            addInterceptStrategy(new HandleFault());
688                        }
689                    }
690                }
691            }
692    
693            // configure delayer
694            if (delayer != null) {
695                Long delayer = CamelContextHelper.parseLong(camelContext, getDelayer());
696                if (delayer != null) {
697                    routeContext.setDelayer(delayer);
698                    if (delayer > 0) {
699                        if (log.isDebugEnabled()) {
700                            log.debug("Delayer is enabled with: " + delayer + " ms. on route: " + this);
701                        }
702                        addInterceptStrategy(new Delayer(delayer));
703                    } else {
704                        if (log.isDebugEnabled()) {
705                            log.debug("Delayer is disabled on route: " + this);
706                        }
707                    }
708                }
709            }
710    
711            // configure route policy
712            if (routePolicy != null) {
713                if (log.isDebugEnabled()) {
714                    log.debug("RoutePolicy is enabled: " + routePolicy + " on route: " + this);
715                }
716                routeContext.setRoutePolicy(getRoutePolicy());
717            } else if (routePolicyRef != null) {
718                RoutePolicy policy = CamelContextHelper.mandatoryLookup(camelContext, routePolicyRef, RoutePolicy.class);
719                if (log.isDebugEnabled()) {
720                    log.debug("RoutePolicy is enabled: " + policy + " on route: " + this);
721                }
722                routeContext.setRoutePolicy(policy);
723            }
724    
725            // configure auto startup
726            Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, getAutoStartup());
727            if (isAutoStartup != null) {
728                if (log.isDebugEnabled()) {
729                    log.debug("Using AutoStartup " + isAutoStartup + " on route: " + this);
730                }
731                routeContext.setAutoStartup(isAutoStartup);
732            }
733    
734            // configure shutdown
735            if (shutdownRoute != null) {
736                if (log.isDebugEnabled()) {
737                    log.debug("Using ShutdownRoute " + getShutdownRoute() + " on route: " + this);
738                }
739                routeContext.setShutdownRoute(getShutdownRoute());
740            }
741            if (shutdownRunningTask != null) {
742                if (log.isDebugEnabled()) {
743                    log.debug("Using ShutdownRunningTask " + getShutdownRunningTask() + " on route: " + this);
744                }
745                routeContext.setShutdownRunningTask(getShutdownRunningTask());
746            }
747    
748            // should inherit the intercept strategies we have defined
749            routeContext.setInterceptStrategies(this.getInterceptStrategies());
750            // force endpoint resolution
751            routeContext.getEndpoint();
752            if (camelContext != null) {
753                for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
754                    strategy.onRouteContextCreate(routeContext);
755                }
756            }
757    
758            // validate route has output processors
759            if (!ProcessorDefinitionHelper.hasOutputs(outputs, true)) {
760                RouteDefinition route = routeContext.getRoute();
761                String at = fromType.toString();
762                Exception cause = new IllegalArgumentException("Route " + route.getId() + " has no output processors."
763                        + " You need to add outputs to the route such as to(\"log:foo\").");
764                throw new FailedToCreateRouteException(route.getId(), route.toString(), at, cause);
765            }
766    
767            List<ProcessorDefinition> list = new ArrayList<ProcessorDefinition>(outputs);
768            for (ProcessorDefinition output : list) {
769                try {
770                    output.addRoutes(routeContext, routes);
771                } catch (Exception e) {
772                    RouteDefinition route = routeContext.getRoute();
773                    throw new FailedToCreateRouteException(route.getId(), route.toString(), output.toString(), e);
774                }
775            }
776    
777            routeContext.commit();
778            return routeContext;
779        }
780    
781    }