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}