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<>();
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<>();
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<>();
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<>();
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            // add the routes to the inflight registry so they are pre-installed
212            for (Route route : routes) {
213                camelContext.getInflightRepository().addRoute(route.getId());
214            }
215        }
216    }
217
218    protected void doStart() throws Exception {
219        warmUp();
220
221        for (Route route : routes) {
222            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
223                // start the route itself
224                ServiceHelper.startService(route);
225
226                // invoke callbacks on route policy
227                if (route.getRouteContext().getRoutePolicyList() != null) {
228                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
229                        routePolicy.onStart(route);
230                    }
231                }
232
233                // fire event
234                EventHelper.notifyRouteStarted(camelContext, route);
235            }
236        }
237    }
238
239    protected void doStop() throws Exception {
240
241        // if we are stopping CamelContext then we are shutting down
242        boolean isShutdownCamelContext = camelContext.isStopping();
243
244        if (isShutdownCamelContext || isRemovingRoutes()) {
245            // need to call onRoutesRemove when the CamelContext is shutting down or Route is shutdown
246            for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
247                strategy.onRoutesRemove(routes);
248            }
249        }
250        
251        for (Route route : routes) {
252            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
253                LOG.debug("Stopping services on route: {}", route.getId());
254
255                // gather list of services to stop as we need to start child services as well
256                Set<Service> services = gatherChildServices(route, true);
257
258                // stop services
259                stopChildService(route, services, isShutdownCamelContext);
260
261                // stop the route itself
262                if (isShutdownCamelContext) {
263                    ServiceHelper.stopAndShutdownServices(route);
264                } else {
265                    ServiceHelper.stopServices(route);
266                }
267
268                // invoke callbacks on route policy
269                if (route.getRouteContext().getRoutePolicyList() != null) {
270                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
271                        routePolicy.onStop(route);
272                    }
273                }
274                // fire event
275                EventHelper.notifyRouteStopped(camelContext, route);
276            }
277        }
278        if (isRemovingRoutes()) {
279            camelContext.removeRouteCollection(routes);
280        }
281        // need to warm up again
282        warmUpDone.set(false);
283    }
284
285    @Override
286    protected void doShutdown() throws Exception {
287        for (Route route : routes) {
288            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
289                LOG.debug("Shutting down services on route: {}", route.getId());
290
291                // gather list of services to stop as we need to start child services as well
292                Set<Service> services = gatherChildServices(route, true);
293
294                // shutdown services
295                stopChildService(route, services, true);
296
297                // shutdown the route itself
298                ServiceHelper.stopAndShutdownServices(route);
299
300                // endpoints should only be stopped when Camel is shutting down
301                // see more details in the warmUp method
302                ServiceHelper.stopAndShutdownServices(route.getEndpoint());
303                // invoke callbacks on route policy
304                if (route.getRouteContext().getRoutePolicyList() != null) {
305                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
306                        routePolicy.onRemove(route);
307                    }
308                }
309
310                // fire event
311                EventHelper.notifyRouteRemoved(camelContext, route);
312            }
313        }
314
315        // need to call onRoutesRemove when the CamelContext is shutting down or Route is shutdown
316        for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
317            strategy.onRoutesRemove(routes);
318        }
319        
320        // remove the routes from the inflight registry
321        for (Route route : routes) {
322            camelContext.getInflightRepository().removeRoute(route.getId());
323        }
324
325        // remove the routes from the collections
326        camelContext.removeRouteCollection(routes);
327        
328        // clear inputs on shutdown
329        inputs.clear();
330        warmUpDone.set(false);
331        endpointDone.set(false);
332    }
333
334    @Override
335    protected void doSuspend() throws Exception {
336        // suspend and resume logic is provided by DefaultCamelContext which leverages ShutdownStrategy
337        // to safely suspend and resume
338        for (Route route : routes) {
339            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
340                if (route.getRouteContext().getRoutePolicyList() != null) {
341                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
342                        routePolicy.onSuspend(route);
343                    }
344                }
345            }
346        }
347    }
348
349    @Override
350    protected void doResume() throws Exception {
351        // suspend and resume logic is provided by DefaultCamelContext which leverages ShutdownStrategy
352        // to safely suspend and resume
353        for (Route route : routes) {
354            try (MDCHelper mdcHelper = new MDCHelper(route.getId())) {
355                if (route.getRouteContext().getRoutePolicyList() != null) {
356                    for (RoutePolicy routePolicy : route.getRouteContext().getRoutePolicyList()) {
357                        routePolicy.onResume(route);
358                    }
359                }
360            }
361        }
362    }
363
364    protected void startChildService(Route route, List<Service> services) throws Exception {
365        for (Service service : services) {
366            LOG.debug("Starting child service on route: {} -> {}", route.getId(), service);
367            for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
368                strategy.onServiceAdd(camelContext, service, route);
369            }
370            ServiceHelper.startService(service);
371            addChildService(service);
372        }
373    }
374
375    protected void stopChildService(Route route, Set<Service> services, boolean shutdown) throws Exception {
376        for (Service service : services) {
377            LOG.debug("{} child service on route: {} -> {}", shutdown ? "Shutting down" : "Stopping", route.getId(), service);
378            if (service instanceof ErrorHandler) {
379                // special for error handlers
380                for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
381                    strategy.onErrorHandlerRemove(route.getRouteContext(), (Processor) service, route.getRouteContext().getRoute().getErrorHandlerBuilder());
382                }
383            } else {
384                for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) {
385                    strategy.onServiceRemove(camelContext, service, route);
386                }
387            }
388            if (shutdown) {
389                ServiceHelper.stopAndShutdownService(service);
390            } else {
391                ServiceHelper.stopService(service);
392            }
393            removeChildService(service);
394        }
395    }
396
397    /**
398     * Gather all child services
399     */
400    private Set<Service> gatherChildServices(Route route, boolean includeErrorHandler) {
401        // gather list of services to stop as we need to start child services as well
402        List<Service> services = new ArrayList<>();
403        services.addAll(route.getServices());
404        // also get route scoped services
405        doGetRouteScopedServices(services, route);
406        Set<Service> list = new LinkedHashSet<>();
407        for (Service service : services) {
408            list.addAll(ServiceHelper.getChildServices(service));
409        }
410        if (includeErrorHandler) {
411            // also get route scoped error handler (which must be done last)
412            doGetRouteScopedErrorHandler(list, route);
413        }
414        Set<Service> answer = new LinkedHashSet<>();
415        answer.addAll(list);
416        return answer;
417    }
418
419    /**
420     * Gather the route scoped error handler from the given route
421     */
422    private void doGetRouteScopedErrorHandler(Set<Service> services, Route route) {
423        // only include error handlers if they are route scoped
424        boolean includeErrorHandler = !routeDefinition.isContextScopedErrorHandler(route.getRouteContext().getCamelContext());
425        List<Service> extra = new ArrayList<>();
426        if (includeErrorHandler) {
427            for (Service service : services) {
428                if (service instanceof Channel) {
429                    Processor eh = ((Channel) service).getErrorHandler();
430                    if (eh instanceof Service) {
431                        extra.add((Service) eh);
432                    }
433                }
434            }
435        }
436        if (!extra.isEmpty()) {
437            services.addAll(extra);
438        }
439    }
440
441    /**
442     * Gather all other kind of route scoped services from the given route, except error handler
443     */
444    private void doGetRouteScopedServices(List<Service> services, Route route) {
445        for (ProcessorDefinition<?> output : route.getRouteContext().getRoute().getOutputs()) {
446            if (output instanceof OnExceptionDefinition) {
447                OnExceptionDefinition onExceptionDefinition = (OnExceptionDefinition) output;
448                if (onExceptionDefinition.isRouteScoped()) {
449                    Processor errorHandler = onExceptionDefinition.getErrorHandler(route.getId());
450                    if (errorHandler instanceof Service) {
451                        services.add((Service) errorHandler);
452                    }
453                }
454            } else if (output instanceof OnCompletionDefinition) {
455                OnCompletionDefinition onCompletionDefinition = (OnCompletionDefinition) output;
456                if (onCompletionDefinition.isRouteScoped()) {
457                    Processor onCompletionProcessor = onCompletionDefinition.getOnCompletion(route.getId());
458                    if (onCompletionProcessor instanceof Service) {
459                        services.add((Service) onCompletionProcessor);
460                    }
461                }
462            }
463        }
464    }
465
466    class MDCHelper implements AutoCloseable {
467        final Map<String, String> originalContextMap;
468
469        MDCHelper(String routeId) {
470            if (getCamelContext().isUseMDCLogging()) {
471                originalContextMap = MDC.getCopyOfContextMap();
472                MDC.put(MDC_CAMEL_CONTEXT_ID, getCamelContext().getName());
473                MDC.put(MDC_ROUTE_ID, routeId);
474            } else {
475                originalContextMap = null;
476            }
477        }
478
479        @Override
480        public void close() {
481            if (getCamelContext().isUseMDCLogging()) {
482                if (originalContextMap != null) {
483                    MDC.setContextMap(originalContextMap);
484                } else {
485                    MDC.clear();
486                }
487            }
488        }
489
490    }
491
492}