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 */ 017 package org.apache.camel.model; 018 019 import java.util.ArrayList; 020 import java.util.Collection; 021 import java.util.List; 022 import java.util.concurrent.atomic.AtomicBoolean; 023 import javax.xml.bind.annotation.XmlAccessType; 024 import javax.xml.bind.annotation.XmlAccessorType; 025 import javax.xml.bind.annotation.XmlAttribute; 026 import javax.xml.bind.annotation.XmlElementRef; 027 import javax.xml.bind.annotation.XmlRootElement; 028 import javax.xml.bind.annotation.XmlTransient; 029 import javax.xml.bind.annotation.XmlType; 030 031 import org.apache.camel.CamelContext; 032 import org.apache.camel.Endpoint; 033 import org.apache.camel.FailedToCreateRouteException; 034 import org.apache.camel.NoSuchEndpointException; 035 import org.apache.camel.Route; 036 import org.apache.camel.ServiceStatus; 037 import org.apache.camel.ShutdownRoute; 038 import org.apache.camel.ShutdownRunningTask; 039 import org.apache.camel.builder.ErrorHandlerBuilder; 040 import org.apache.camel.builder.ErrorHandlerBuilderRef; 041 import org.apache.camel.builder.RouteBuilder; 042 import org.apache.camel.impl.DefaultRouteContext; 043 import org.apache.camel.processor.interceptor.Delayer; 044 import org.apache.camel.processor.interceptor.HandleFault; 045 import org.apache.camel.processor.interceptor.StreamCaching; 046 import org.apache.camel.spi.LifecycleStrategy; 047 import org.apache.camel.spi.RouteContext; 048 import org.apache.camel.spi.RoutePolicy; 049 import org.apache.camel.util.CamelContextHelper; 050 import org.apache.camel.util.ObjectHelper; 051 052 /** 053 * Represents an XML <route/> element 054 * 055 * @version $Revision: 1021867 $ 056 */ 057 @XmlRootElement(name = "route") 058 @XmlType(propOrder = {"inputs", "outputs" }) 059 @XmlAccessorType(XmlAccessType.PROPERTY) 060 public class RouteDefinition extends ProcessorDefinition<RouteDefinition> { 061 private final AtomicBoolean prepared = new AtomicBoolean(false); 062 private List<FromDefinition> inputs = new ArrayList<FromDefinition>(); 063 private List<ProcessorDefinition> outputs = new ArrayList<ProcessorDefinition>(); 064 private String group; 065 private String streamCache; 066 private String trace; 067 private String handleFault; 068 private String delayer; 069 private String autoStartup; 070 private Integer startupOrder; 071 private RoutePolicy routePolicy; 072 private String routePolicyRef; 073 private ShutdownRoute shutdownRoute; 074 private ShutdownRunningTask shutdownRunningTask; 075 076 public RouteDefinition() { 077 } 078 079 public RouteDefinition(String uri) { 080 from(uri); 081 } 082 083 public RouteDefinition(Endpoint endpoint) { 084 from(endpoint); 085 } 086 087 /** 088 * Prepares the route definition to be ready to be added to {@link CamelContext} 089 */ 090 public void prepare() { 091 if (prepared.compareAndSet(false, true)) { 092 // at first init the parent 093 RouteDefinitionHelper.initParent(this); 094 095 // abstracts is the cross cutting concerns 096 List<ProcessorDefinition> abstracts = new ArrayList<ProcessorDefinition>(); 097 098 // upper is the cross cutting concerns such as interceptors, error handlers etc 099 List<ProcessorDefinition> upper = new ArrayList<ProcessorDefinition>(); 100 101 // lower is the regular route 102 List<ProcessorDefinition> lower = new ArrayList<ProcessorDefinition>(); 103 104 RouteDefinitionHelper.prepareRouteForInit(this, abstracts, lower); 105 106 // rebuild route as upper + lower 107 this.clearOutput(); 108 this.getOutputs().addAll(lower); 109 this.getOutputs().addAll(0, upper); 110 } 111 } 112 113 /** 114 * Marks the route definition as already prepared, for example using custom logic 115 * such as a {@link RouteBuilder} or from <tt>camel-core-xml</tt> component. 116 */ 117 public void customPrepared() { 118 prepared.set(true); 119 } 120 121 @Override 122 public String toString() { 123 return "Route[" + inputs + " -> " + outputs + "]"; 124 } 125 126 @Override 127 public String getShortName() { 128 return "route"; 129 } 130 131 /** 132 * Returns the status of the route if it has been registered with a {@link CamelContext} 133 */ 134 public ServiceStatus getStatus(CamelContext camelContext) { 135 if (camelContext != null) { 136 ServiceStatus answer = camelContext.getRouteStatus(this.getId()); 137 if (answer == null) { 138 answer = ServiceStatus.Stopped; 139 } 140 return answer; 141 } 142 return null; 143 } 144 145 public boolean isStartable(CamelContext camelContext) { 146 ServiceStatus status = getStatus(camelContext); 147 if (status == null) { 148 return true; 149 } else { 150 return status.isStartable(); 151 } 152 } 153 154 public boolean isStoppable(CamelContext camelContext) { 155 ServiceStatus status = getStatus(camelContext); 156 if (status == null) { 157 return false; 158 } else { 159 return status.isStoppable(); 160 } 161 } 162 163 public List<RouteContext> addRoutes(CamelContext camelContext, Collection<Route> routes) throws Exception { 164 List<RouteContext> answer = new ArrayList<RouteContext>(); 165 166 ErrorHandlerBuilder handler = camelContext.getErrorHandlerBuilder(); 167 if (handler != null) { 168 setErrorHandlerBuilderIfNull(handler); 169 } 170 171 for (FromDefinition fromType : inputs) { 172 RouteContext routeContext; 173 try { 174 routeContext = addRoutes(camelContext, routes, fromType); 175 } catch (FailedToCreateRouteException e) { 176 throw e; 177 } catch (Exception e) { 178 // wrap in exception which provide more details about which route was failing 179 throw new FailedToCreateRouteException(getId(), toString(), e); 180 } 181 answer.add(routeContext); 182 } 183 return answer; 184 } 185 186 187 public Endpoint resolveEndpoint(CamelContext camelContext, String uri) throws NoSuchEndpointException { 188 ObjectHelper.notNull(camelContext, "CamelContext"); 189 return CamelContextHelper.getMandatoryEndpoint(camelContext, uri); 190 } 191 192 /** 193 * Advices this route with the route builder. 194 * <p/> 195 * The advice process will add the interceptors, on exceptions, on completions etc. configured 196 * from the route builder to this route. 197 * <p/> 198 * This is mostly used for testing purpose to add interceptors and the likes to an existing route. 199 * <p/> 200 * Will stop and remove the old route from camel context and add and start this new advised route. 201 * 202 * @param camelContext the camel context 203 * @param builder the route builder 204 * @return a new route which is this route merged with the route builder 205 * @throws Exception can be thrown from the route builder 206 */ 207 public RouteDefinition adviceWith(CamelContext camelContext, RouteBuilder builder) throws Exception { 208 ObjectHelper.notNull(camelContext, "CamelContext"); 209 ObjectHelper.notNull(builder, "RouteBuilder"); 210 211 // configure and prepare the routes from the builder 212 RoutesDefinition routes = builder.configureRoutes(camelContext); 213 214 // we can only advice with a route builder without any routes 215 if (!routes.getRoutes().isEmpty()) { 216 throw new IllegalArgumentException("You can only advice from a RouteBuilder which has no existing routes." 217 + " Remove all routes from the route builder."); 218 } 219 220 // stop and remove this existing route 221 camelContext.removeRouteDefinition(this); 222 223 // now merge which also ensures that interceptors and the likes get mixed in correctly as well 224 RouteDefinition merged = routes.route(this); 225 226 // add the new merged route 227 camelContext.getRouteDefinitions().add(0, merged); 228 229 // and start it 230 camelContext.startRoute(merged); 231 return merged; 232 } 233 234 // Fluent API 235 // ----------------------------------------------------------------------- 236 237 /** 238 * Creates an input to the route 239 * 240 * @param uri the from uri 241 * @return the builder 242 */ 243 public RouteDefinition from(String uri) { 244 getInputs().add(new FromDefinition(uri)); 245 return this; 246 } 247 248 /** 249 * Creates an input to the route 250 * 251 * @param endpoint the from endpoint 252 * @return the builder 253 */ 254 public RouteDefinition from(Endpoint endpoint) { 255 getInputs().add(new FromDefinition(endpoint)); 256 return this; 257 } 258 259 /** 260 * Creates inputs to the route 261 * 262 * @param uris the from uris 263 * @return the builder 264 */ 265 public RouteDefinition from(String... uris) { 266 for (String uri : uris) { 267 getInputs().add(new FromDefinition(uri)); 268 } 269 return this; 270 } 271 272 /** 273 * Creates inputs to the route 274 * 275 * @param endpoints the from endpoints 276 * @return the builder 277 */ 278 public RouteDefinition from(Endpoint... endpoints) { 279 for (Endpoint endpoint : endpoints) { 280 getInputs().add(new FromDefinition(endpoint)); 281 } 282 return this; 283 } 284 285 /** 286 * Set the group name for this route 287 * 288 * @param name the group name 289 * @return the builder 290 */ 291 public RouteDefinition group(String name) { 292 setGroup(name); 293 return this; 294 } 295 296 /** 297 * Set the route id for this route 298 * 299 * @param id the route id 300 * @return the builder 301 */ 302 public RouteDefinition routeId(String id) { 303 setId(id); 304 return this; 305 } 306 307 /** 308 * Disable stream caching for this route. 309 * 310 * @return the builder 311 */ 312 public RouteDefinition noStreamCaching() { 313 setStreamCache("false"); 314 StreamCaching.noStreamCaching(getInterceptStrategies()); 315 return this; 316 } 317 318 /** 319 * Enable stream caching for this route. 320 * 321 * @return the builder 322 */ 323 public RouteDefinition streamCaching() { 324 setStreamCache("true"); 325 StreamCaching cache = StreamCaching.getStreamCaching(getInterceptStrategies()); 326 if (cache == null) { 327 cache = new StreamCaching(); 328 } 329 330 getInterceptStrategies().add(cache); 331 return this; 332 } 333 334 /** 335 * Disable tracing for this route. 336 * 337 * @return the builder 338 */ 339 public RouteDefinition noTracing() { 340 setTrace("false"); 341 return this; 342 } 343 344 /** 345 * Enable tracing for this route. 346 * 347 * @return the builder 348 */ 349 public RouteDefinition tracing() { 350 setTrace("true"); 351 return this; 352 } 353 354 /** 355 * Disable handle fault for this route. 356 * 357 * @return the builder 358 */ 359 public RouteDefinition noHandleFault() { 360 setHandleFault("false"); 361 return this; 362 } 363 364 /** 365 * Enable handle fault for this route. 366 * 367 * @return the builder 368 */ 369 public RouteDefinition handleFault() { 370 setHandleFault("true"); 371 return this; 372 } 373 374 /** 375 * Disable delayer for this route. 376 * 377 * @return the builder 378 */ 379 public RouteDefinition noDelayer() { 380 setDelayer("0"); 381 return this; 382 } 383 384 /** 385 * Enable delayer for this route. 386 * 387 * @param delay delay in millis 388 * @return the builder 389 */ 390 public RouteDefinition delayer(long delay) { 391 setDelayer("" + delay); 392 return this; 393 } 394 395 /** 396 * Installs the given <a href="http://camel.apache.org/error-handler.html">error handler</a> builder. 397 * 398 * @param errorHandlerBuilder the error handler to be used by default for all child routes 399 * @return the current builder with the error handler configured 400 */ 401 public RouteDefinition errorHandler(ErrorHandlerBuilder errorHandlerBuilder) { 402 setErrorHandlerBuilder(errorHandlerBuilder); 403 return this; 404 } 405 406 /** 407 * Disables this route from being auto started when Camel starts. 408 * 409 * @return the builder 410 */ 411 public RouteDefinition noAutoStartup() { 412 setAutoStartup("false"); 413 return this; 414 } 415 416 /** 417 * Configures the startup order for this route 418 * <p/> 419 * Camel will reorder routes and star them ordered by 0..N where 0 is the lowest number and N the highest number. 420 * Camel will stop routes in reverse order when its stopping. 421 * 422 * @param order the order represented as a number 423 * @return the builder 424 */ 425 public RouteDefinition startupOrder(int order) { 426 setStartupOrder(order); 427 return this; 428 } 429 430 /** 431 * Configures a route policy for this route 432 * 433 * @param routePolicy the route policy 434 * @return the builder 435 */ 436 public RouteDefinition routePolicy(RoutePolicy routePolicy) { 437 setRoutePolicy(routePolicy); 438 return this; 439 } 440 441 /** 442 * Configures a route policy for this route 443 * 444 * @param routePolicyRef reference to a {@link RoutePolicy} to lookup and use. 445 * @return the builder 446 */ 447 public RouteDefinition routePolicyRef(String routePolicyRef) { 448 setRoutePolicyRef(routePolicyRef); 449 return this; 450 } 451 452 /** 453 * Configures a shutdown route option. 454 * 455 * @param shutdownRoute the option to use when shutting down this route 456 * @return the builder 457 */ 458 public RouteDefinition shutdownRoute(ShutdownRoute shutdownRoute) { 459 setShutdownRoute(shutdownRoute); 460 return this; 461 } 462 463 /** 464 * Configures a shutdown running task option. 465 * 466 * @param shutdownRunningTask the option to use when shutting down and how to act upon running tasks. 467 * @return the builder 468 */ 469 public RouteDefinition shutdownRunningTask(ShutdownRunningTask shutdownRunningTask) { 470 setShutdownRunningTask(shutdownRunningTask); 471 return this; 472 } 473 474 // Properties 475 // ----------------------------------------------------------------------- 476 477 public List<FromDefinition> getInputs() { 478 return inputs; 479 } 480 481 @XmlElementRef 482 public void setInputs(List<FromDefinition> inputs) { 483 this.inputs = inputs; 484 } 485 486 public List<ProcessorDefinition> getOutputs() { 487 return outputs; 488 } 489 490 @XmlElementRef 491 public void setOutputs(List<ProcessorDefinition> outputs) { 492 this.outputs = outputs; 493 494 if (outputs != null) { 495 for (ProcessorDefinition output : outputs) { 496 configureChild(output); 497 } 498 } 499 } 500 501 /** 502 * The group that this route belongs to; could be the name of the RouteBuilder class 503 * or be explicitly configured in the XML. 504 * 505 * May be null. 506 */ 507 public String getGroup() { 508 return group; 509 } 510 511 @XmlAttribute 512 public void setGroup(String group) { 513 this.group = group; 514 } 515 516 public String getStreamCache() { 517 return streamCache; 518 } 519 520 @XmlAttribute 521 public void setStreamCache(String streamCache) { 522 this.streamCache = streamCache; 523 } 524 525 public String getTrace() { 526 return trace; 527 } 528 529 @XmlAttribute 530 public void setTrace(String trace) { 531 this.trace = trace; 532 } 533 534 public String getHandleFault() { 535 return handleFault; 536 } 537 538 @XmlAttribute 539 public void setHandleFault(String handleFault) { 540 this.handleFault = handleFault; 541 } 542 543 public String getDelayer() { 544 return delayer; 545 } 546 547 @XmlAttribute 548 public void setDelayer(String delayer) { 549 this.delayer = delayer; 550 } 551 552 public String getAutoStartup() { 553 return autoStartup; 554 } 555 556 public boolean isAutoStartup(CamelContext camelContext) throws Exception { 557 if (getAutoStartup() == null) { 558 // should auto startup by default 559 return true; 560 } 561 Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, getAutoStartup()); 562 return isAutoStartup != null && isAutoStartup; 563 } 564 565 @XmlAttribute 566 public void setAutoStartup(String autoStartup) { 567 this.autoStartup = autoStartup; 568 } 569 570 public Integer getStartupOrder() { 571 return startupOrder; 572 } 573 574 @XmlAttribute 575 public void setStartupOrder(Integer startupOrder) { 576 this.startupOrder = startupOrder; 577 } 578 579 /** 580 * Sets the bean ref name of the error handler builder to use on this route 581 */ 582 @XmlAttribute 583 public void setErrorHandlerRef(String errorHandlerRef) { 584 this.errorHandlerRef = errorHandlerRef; 585 // we use an specific error handler ref (from Spring DSL) then wrap that 586 // with a error handler build ref so Camel knows its not just the default one 587 setErrorHandlerBuilder(new ErrorHandlerBuilderRef(errorHandlerRef)); 588 } 589 590 public String getErrorHandlerRef() { 591 return errorHandlerRef; 592 } 593 594 /** 595 * Sets the error handler if one is not already set 596 */ 597 protected void setErrorHandlerBuilderIfNull(ErrorHandlerBuilder errorHandlerBuilder) { 598 if (this.errorHandlerBuilder == null) { 599 setErrorHandlerBuilder(errorHandlerBuilder); 600 } 601 } 602 603 @XmlAttribute 604 public void setRoutePolicyRef(String routePolicyRef) { 605 this.routePolicyRef = routePolicyRef; 606 } 607 608 public String getRoutePolicyRef() { 609 return routePolicyRef; 610 } 611 612 @XmlTransient 613 public void setRoutePolicy(RoutePolicy routePolicy) { 614 this.routePolicy = routePolicy; 615 } 616 617 public RoutePolicy getRoutePolicy() { 618 return routePolicy; 619 } 620 621 public ShutdownRoute getShutdownRoute() { 622 return shutdownRoute; 623 } 624 625 @XmlAttribute 626 public void setShutdownRoute(ShutdownRoute shutdownRoute) { 627 this.shutdownRoute = shutdownRoute; 628 } 629 630 public ShutdownRunningTask getShutdownRunningTask() { 631 return shutdownRunningTask; 632 } 633 634 @XmlAttribute 635 public void setShutdownRunningTask(ShutdownRunningTask shutdownRunningTask) { 636 this.shutdownRunningTask = shutdownRunningTask; 637 } 638 639 // Implementation methods 640 // ------------------------------------------------------------------------- 641 @SuppressWarnings("unchecked") 642 protected RouteContext addRoutes(CamelContext camelContext, Collection<Route> routes, FromDefinition fromType) throws Exception { 643 RouteContext routeContext = new DefaultRouteContext(camelContext, this, fromType, routes); 644 645 // configure tracing 646 if (trace != null) { 647 Boolean isTrace = CamelContextHelper.parseBoolean(camelContext, getTrace()); 648 if (isTrace != null) { 649 routeContext.setTracing(isTrace); 650 if (isTrace) { 651 if (log.isDebugEnabled()) { 652 log.debug("Tracing is enabled on route: " + this); 653 } 654 // tracing is added in the DefaultChannel so we can enable it on the fly 655 } 656 } 657 } 658 659 // configure stream caching 660 if (streamCache != null) { 661 Boolean isStreamCache = CamelContextHelper.parseBoolean(camelContext, getStreamCache()); 662 if (isStreamCache != null) { 663 routeContext.setStreamCaching(isStreamCache); 664 if (isStreamCache) { 665 if (log.isDebugEnabled()) { 666 log.debug("StreamCaching is enabled on route: " + this); 667 } 668 // only add a new stream cache if not already a global configured on camel context 669 if (StreamCaching.getStreamCaching(camelContext) == null) { 670 addInterceptStrategy(new StreamCaching()); 671 } 672 } 673 } 674 } 675 676 // configure handle fault 677 if (handleFault != null) { 678 Boolean isHandleFault = CamelContextHelper.parseBoolean(camelContext, getHandleFault()); 679 if (isHandleFault != null) { 680 routeContext.setHandleFault(isHandleFault); 681 if (isHandleFault) { 682 if (log.isDebugEnabled()) { 683 log.debug("HandleFault is enabled on route: " + this); 684 } 685 // only add a new handle fault if not already a global configured on camel context 686 if (HandleFault.getHandleFault(camelContext) == null) { 687 addInterceptStrategy(new HandleFault()); 688 } 689 } 690 } 691 } 692 693 // configure delayer 694 if (delayer != null) { 695 Long delayer = CamelContextHelper.parseLong(camelContext, getDelayer()); 696 if (delayer != null) { 697 routeContext.setDelayer(delayer); 698 if (delayer > 0) { 699 if (log.isDebugEnabled()) { 700 log.debug("Delayer is enabled with: " + delayer + " ms. on route: " + this); 701 } 702 addInterceptStrategy(new Delayer(delayer)); 703 } else { 704 if (log.isDebugEnabled()) { 705 log.debug("Delayer is disabled on route: " + this); 706 } 707 } 708 } 709 } 710 711 // configure route policy 712 if (routePolicy != null) { 713 if (log.isDebugEnabled()) { 714 log.debug("RoutePolicy is enabled: " + routePolicy + " on route: " + this); 715 } 716 routeContext.setRoutePolicy(getRoutePolicy()); 717 } else if (routePolicyRef != null) { 718 RoutePolicy policy = CamelContextHelper.mandatoryLookup(camelContext, routePolicyRef, RoutePolicy.class); 719 if (log.isDebugEnabled()) { 720 log.debug("RoutePolicy is enabled: " + policy + " on route: " + this); 721 } 722 routeContext.setRoutePolicy(policy); 723 } 724 725 // configure auto startup 726 Boolean isAutoStartup = CamelContextHelper.parseBoolean(camelContext, getAutoStartup()); 727 if (isAutoStartup != null) { 728 if (log.isDebugEnabled()) { 729 log.debug("Using AutoStartup " + isAutoStartup + " on route: " + this); 730 } 731 routeContext.setAutoStartup(isAutoStartup); 732 } 733 734 // configure shutdown 735 if (shutdownRoute != null) { 736 if (log.isDebugEnabled()) { 737 log.debug("Using ShutdownRoute " + getShutdownRoute() + " on route: " + this); 738 } 739 routeContext.setShutdownRoute(getShutdownRoute()); 740 } 741 if (shutdownRunningTask != null) { 742 if (log.isDebugEnabled()) { 743 log.debug("Using ShutdownRunningTask " + getShutdownRunningTask() + " on route: " + this); 744 } 745 routeContext.setShutdownRunningTask(getShutdownRunningTask()); 746 } 747 748 // should inherit the intercept strategies we have defined 749 routeContext.setInterceptStrategies(this.getInterceptStrategies()); 750 // force endpoint resolution 751 routeContext.getEndpoint(); 752 if (camelContext != null) { 753 for (LifecycleStrategy strategy : camelContext.getLifecycleStrategies()) { 754 strategy.onRouteContextCreate(routeContext); 755 } 756 } 757 758 // validate route has output processors 759 if (!ProcessorDefinitionHelper.hasOutputs(outputs, true)) { 760 RouteDefinition route = routeContext.getRoute(); 761 String at = fromType.toString(); 762 Exception cause = new IllegalArgumentException("Route " + route.getId() + " has no output processors." 763 + " You need to add outputs to the route such as to(\"log:foo\")."); 764 throw new FailedToCreateRouteException(route.getId(), route.toString(), at, cause); 765 } 766 767 List<ProcessorDefinition> list = new ArrayList<ProcessorDefinition>(outputs); 768 for (ProcessorDefinition output : list) { 769 try { 770 output.addRoutes(routeContext, routes); 771 } catch (Exception e) { 772 RouteDefinition route = routeContext.getRoute(); 773 throw new FailedToCreateRouteException(route.getId(), route.toString(), output.toString(), e); 774 } 775 } 776 777 routeContext.commit(); 778 return routeContext; 779 } 780 781 }