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}