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.impl;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.LinkedHashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.concurrent.atomic.AtomicBoolean;
027
028import org.apache.camel.CamelContext;
029import org.apache.camel.Channel;
030import org.apache.camel.Consumer;
031import org.apache.camel.Endpoint;
032import org.apache.camel.EndpointAware;
033import org.apache.camel.FailedToCreateRouteException;
034import org.apache.camel.Processor;
035import org.apache.camel.Route;
036import org.apache.camel.RouteAware;
037import org.apache.camel.Service;
038import org.apache.camel.model.OnCompletionDefinition;
039import org.apache.camel.model.OnExceptionDefinition;
040import org.apache.camel.model.ProcessorDefinition;
041import org.apache.camel.model.RouteDefinition;
042import org.apache.camel.processor.ErrorHandler;
043import org.apache.camel.spi.LifecycleStrategy;
044import org.apache.camel.spi.RouteContext;
045import org.apache.camel.spi.RoutePolicy;
046import org.apache.camel.support.ChildServiceSupport;
047import org.apache.camel.util.EventHelper;
048import org.apache.camel.util.ServiceHelper;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051import org.slf4j.MDC;
052
053import static org.apache.camel.impl.MDCUnitOfWork.MDC_CAMEL_CONTEXT_ID;
054import static org.apache.camel.impl.MDCUnitOfWork.MDC_ROUTE_ID;
055
056/**
057 * Represents the runtime objects for a given {@link RouteDefinition} so that it can be stopped independently
058 * of other routes
059 *
060 * @version 
061 */
062public class RouteService extends ChildServiceSupport {
063
064    private static final Logger LOG = LoggerFactory.getLogger(RouteService.class);
065
066    private final DefaultCamelContext camelContext;
067    private final RouteDefinition routeDefinition;
068    private final List<RouteContext> routeContexts;
069    private final List<Route> routes;
070    private final String id;
071    private boolean removingRoutes;
072    private final Map<Route, Consumer> inputs = new HashMap<Route, Consumer>();
073    private final AtomicBoolean warmUpDone = new AtomicBoolean(false);
074    private final AtomicBoolean endpointDone = new AtomicBoolean(false);
075
076    public RouteService(DefaultCamelContext camelContext, RouteDefinition routeDefinition, List<RouteContext> routeContexts, List<Route> routes) {
077        this.camelContext = camelContext;
078        this.routeDefinition = routeDefinition;
079        this.routeContexts = routeContexts;
080        this.routes = routes;
081        this.id = routeDefinition.idOrCreate(camelContext.getNodeIdFactory());
082    }
083
084    public String getId() {
085        return id;
086    }
087
088    public CamelContext getCamelContext() {
089        return camelContext;
090    }
091
092    public List<RouteContext> getRouteContexts() {
093        return routeContexts;
094    }
095
096    public RouteDefinition getRouteDefinition() {
097        return routeDefinition;
098    }
099
100    public Collection<Route> getRoutes() {
101        return routes;
102    }
103
104    /**
105     * Gather all the endpoints this route service uses
106     * <p/>
107     * This implementation finds the endpoints by searching all the child services
108     * for {@link org.apache.camel.EndpointAware} processors which uses an endpoint.
109     */
110    public Set<Endpoint> gatherEndpoints() {
111        Set<Endpoint> answer = new LinkedHashSet<Endpoint>();
112        for (Route route : routes) {
113            Set<Service> services = gatherChildServices(route, true);
114            for (Service service : services) {
115                if (service instanceof EndpointAware) {
116                    Endpoint endpoint = ((EndpointAware) service).getEndpoint();
117                    if (endpoint != null) {
118                        answer.add(endpoint);
119                    }
120                }
121            }
122        }
123        return answer;
124    }
125
126    /**
127     * Gets the inputs to the routes.
128     *
129     * @return list of {@link Consumer} as inputs for the routes
130     */
131    public Map<Route, Consumer> getInputs() {
132        return inputs;
133    }
134
135    public boolean isRemovingRoutes() {
136        return removingRoutes;
137    }
138
139    public void setRemovingRoutes(boolean removingRoutes) {
140        this.removingRoutes = removingRoutes;
141    }
142
143    public void warmUp() throws Exception {
144        try {
145            doWarmUp();
146        } catch (Exception e) {
147            throw new FailedToCreateRouteException(routeDefinition.getId(), routeDefinition.toString(), e);
148        }
149    }
150
151    protected synchronized void doWarmUp() throws Exception {
152        if (endpointDone.compareAndSet(false, true)) {
153            // endpoints should only be started once as they can be reused on other routes
154            // and whatnot, thus their lifecycle is to start once, and only to stop when Camel shutdown
155            for (Route route : routes) {
156                // ensure endpoint is started first (before the route services, such as the consumer)
157                ServiceHelper.startService(route.getEndpoint());
158            }
159        }
160
161        if (warmUpDone.compareAndSet(false, true)) {
162
163            for (Route route : routes) {
164                try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
165                    // warm up the route first
166                    route.warmUp();
167
168                    LOG.debug("Starting services on route: {}", route.getId());
169                    List<Service> services = route.getServices();
170
171                    // callback that we are staring these services
172                    route.onStartingServices(services);
173
174                    // gather list of services to start as we need to start child services as well
175                    Set<Service> list = new LinkedHashSet<Service>();
176                    for (Service service : services) {
177                        list.addAll(ServiceHelper.getChildServices(service));
178                    }
179
180                    // split into consumers and child services as we need to start the consumers
181                    // afterwards to avoid them being active while the others start
182                    List<Service> childServices = new ArrayList<Service>();
183                    for (Service service : list) {
184
185                        // inject the route
186                        if (service instanceof RouteAware) {
187                            ((RouteAware) service).setRoute(route);
188                        }
189
190                        if (service instanceof Consumer) {
191                            inputs.put(route, (Consumer) service);
192                        } else {
193                            childServices.add(service);
194                        }
195                    }
196                    startChildService(route, childServices);
197
198                    // fire event
199                    EventHelper.notifyRouteAdded(camelContext, route);
200                }
201            }
202
203            // ensure lifecycle strategy is invoked which among others enlist the route in JMX
204            for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
205                strategy.onRoutesAdd(routes);
206            }
207
208            // add routes to camel context
209            camelContext.addRouteCollection(routes);
210        }
211    }
212
213    protected void doStart() throws Exception {
214        warmUp();
215
216        for (Route route : routes) {
217            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
218                // start the route itself
219                ServiceHelper.startService(route);
220
221                // invoke callbacks on route policy
222                if (route.getRouteContext().getRoutePolicyList() != null) {
223                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
224                        routePolicy.onStart(route);
225                    }
226                }
227
228                // fire event
229                EventHelper.notifyRouteStarted(camelContext, route);
230            }
231        }
232    }
233
234    protected void doStop() throws Exception {
235
236        // if we are stopping CamelContext then we are shutting down
237        boolean isShutdownCamelContext = camelContext.isStopping();
238
239        if (isShutdownCamelContext || isRemovingRoutes()) {
240            // need to call onRoutesRemove when the CamelContext is shutting down or Route is shutdown
241            for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
242                strategy.onRoutesRemove(routes);
243            }
244        }
245        
246        for (Route route : routes) {
247            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
248                LOG.debug("Stopping services on route: {}", route.getId());
249
250                // gather list of services to stop as we need to start child services as well
251                Set<Service> services = gatherChildServices(route, true);
252
253                // stop services
254                stopChildService(route, services, isShutdownCamelContext);
255
256                // stop the route itself
257                if (isShutdownCamelContext) {
258                    ServiceHelper.stopAndShutdownServices(route);
259                } else {
260                    ServiceHelper.stopServices(route);
261                }
262
263                // invoke callbacks on route policy
264                if (route.getRouteContext().getRoutePolicyList() != null) {
265                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
266                        routePolicy.onStop(route);
267                    }
268                }
269                // fire event
270                EventHelper.notifyRouteStopped(camelContext, route);
271            }
272        }
273        if (isRemovingRoutes()) {
274            camelContext.removeRouteCollection(routes);
275        }
276        // need to warm up again
277        warmUpDone.set(false);
278    }
279
280    @Override
281    protected void doShutdown() throws Exception {
282        for (Route route : routes) {
283            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
284                LOG.debug("Shutting down services on route: {}", route.getId());
285
286                // gather list of services to stop as we need to start child services as well
287                Set<Service> services = gatherChildServices(route, true);
288
289                // shutdown services
290                stopChildService(route, services, true);
291
292                // shutdown the route itself
293                ServiceHelper.stopAndShutdownServices(route);
294
295                // endpoints should only be stopped when Camel is shutting down
296                // see more details in the warmUp method
297                ServiceHelper.stopAndShutdownServices(route.getEndpoint());
298                // invoke callbacks on route policy
299                if (route.getRouteContext().getRoutePolicyList() != null) {
300                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
301                        routePolicy.onRemove(route);
302                    }
303                }
304                // fire event
305                EventHelper.notifyRouteRemoved(camelContext, route);
306            }
307        }
308
309        // need to call onRoutesRemove when the CamelContext is shutting down or Route is shutdown
310        for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
311            strategy.onRoutesRemove(routes);
312        }
313        
314        // remove the routes from the inflight registry
315        for (Route route : routes) {
316            camelContext.getInflightRepository().removeRoute(route.getId());
317        }
318
319        // remove the routes from the collections
320        camelContext.removeRouteCollection(routes);
321        
322        // clear inputs on shutdown
323        inputs.clear();
324        warmUpDone.set(false);
325        endpointDone.set(false);
326    }
327
328    @Override
329    protected void doSuspend() throws Exception {
330        // suspend and resume logic is provided by DefaultCamelContext which leverages ShutdownStrategy
331        // to safely suspend and resume
332        for (Route route : routes) {
333            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
334                if (route.getRouteContext().getRoutePolicyList() != null) {
335                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
336                        routePolicy.onSuspend(route);
337                    }
338                }
339            }
340        }
341    }
342
343    @Override
344    protected void doResume() throws Exception {
345        // suspend and resume logic is provided by DefaultCamelContext which leverages ShutdownStrategy
346        // to safely suspend and resume
347        for (Route route : routes) {
348            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
349                if (route.getRouteContext().getRoutePolicyList() != null) {
350                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
351                        routePolicy.onResume(route);
352                    }
353                }
354            }
355        }
356    }
357
358    protected void startChildService(Route route, List<Service> services) throws Exception {
359        for (Service service : services) {
360            LOG.debug("Starting child service on route: {} -> {}", route.getId(), service);
361            for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
362                strategy.onServiceAdd(camelContext, service, route);
363            }
364            ServiceHelper.startService(service);
365            addChildService(service);
366        }
367    }
368
369    protected void stopChildService(Route route, Set<Service> services, boolean shutdown) throws Exception {
370        for (Service service : services) {
371            LOG.debug("{} child service on route: {} -> {}", new Object[]{shutdown ? "Shutting down" : "Stopping", route.getId(), service});
372            if (service instanceof ErrorHandler) {
373                // special for error handlers
374                for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
375                    strategy.onErrorHandlerRemove(route.getRouteContext(), (Processor) service, route.getRouteContext().getRoute().getErrorHandlerBuilder());
376                }
377            } else {
378                for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
379                    strategy.onServiceRemove(camelContext, service, route);
380                }
381            }
382            if (shutdown) {
383                ServiceHelper.stopAndShutdownService(service);
384            } else {
385                ServiceHelper.stopService(service);
386            }
387            removeChildService(service);
388        }
389    }
390
391    /**
392     * Gather all child services
393     */
394    private Set<Service> gatherChildServices(Route route, boolean includeErrorHandler) {
395        // gather list of services to stop as we need to start child services as well
396        List<Service> services = new ArrayList<Service>();
397        services.addAll(route.getServices());
398        // also get route scoped services
399        doGetRouteScopedServices(services, route);
400        Set<Service> list = new LinkedHashSet<Service>();
401        for (Service service : services) {
402            list.addAll(ServiceHelper.getChildServices(service));
403        }
404        if (includeErrorHandler) {
405            // also get route scoped error handler (which must be done last)
406            doGetRouteScopedErrorHandler(list, route);
407        }
408        Set<Service> answer = new LinkedHashSet<Service>();
409        answer.addAll(list);
410        return answer;
411    }
412
413    /**
414     * Gather the route scoped error handler from the given route
415     */
416    private void doGetRouteScopedErrorHandler(Set<Service> services, Route route) {
417        // only include error handlers if they are route scoped
418        boolean includeErrorHandler = !routeDefinition.isContextScopedErrorHandler(route.getRouteContext().getCamelContext());
419        List<Service> extra = new ArrayList<Service>();
420        if (includeErrorHandler) {
421            for (Service service : services) {
422                if (service instanceof Channel) {
423                    Processor eh = ((Channel) service).getErrorHandler();
424                    if (eh != null && eh instanceof Service) {
425                        extra.add((Service) eh);
426                    }
427                }
428            }
429        }
430        if (!extra.isEmpty()) {
431            services.addAll(extra);
432        }
433    }
434
435    /**
436     * Gather all other kind of route scoped services from the given route, except error handler
437     */
438    private void doGetRouteScopedServices(List<Service> services, Route route) {
439        for (ProcessorDefinition<?> output : route.getRouteContext().getRoute().getOutputs()) {
440            if (output instanceof OnExceptionDefinition) {
441                OnExceptionDefinition onExceptionDefinition = (OnExceptionDefinition) output;
442                if (onExceptionDefinition.isRouteScoped()) {
443                    Processor errorHandler = onExceptionDefinition.getErrorHandler(route.getId());
444                    if (errorHandler != null && errorHandler instanceof Service) {
445                        services.add((Service) errorHandler);
446                    }
447                }
448            } else if (output instanceof OnCompletionDefinition) {
449                OnCompletionDefinition onCompletionDefinition = (OnCompletionDefinition) output;
450                if (onCompletionDefinition.isRouteScoped()) {
451                    Processor onCompletionProcessor = onCompletionDefinition.getOnCompletion(route.getId());
452                    if (onCompletionProcessor != null && onCompletionProcessor instanceof Service) {
453                        services.add((Service) onCompletionProcessor);
454                    }
455                }
456            }
457        }
458    }
459
460    class MDCHelper implements AutoCloseable {
461        final Map<String, String> originalContextMap;
462
463        MDCHelper(String routeId) {
464            if (getCamelContext().isUseMDCLogging()) {
465                originalContextMap = MDC.getCopyOfContextMap();
466                MDC.put(MDC_CAMEL_CONTEXT_ID, getCamelContext().getName());
467                MDC.put(MDC_ROUTE_ID, routeId);
468            } else {
469                originalContextMap = null;
470            }
471        }
472
473        @Override
474        public void close() {
475            if (getCamelContext().isUseMDCLogging()) {
476                if (originalContextMap != null) {
477                    MDC.setContextMap(originalContextMap);
478                } else {
479                    MDC.clear();
480                }
481            }
482        }
483
484    }
485
486}