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