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.model; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.concurrent.ExecutorService; 022import javax.xml.bind.annotation.XmlAccessType; 023import javax.xml.bind.annotation.XmlAccessorType; 024import javax.xml.bind.annotation.XmlAttribute; 025import javax.xml.bind.annotation.XmlRootElement; 026import javax.xml.bind.annotation.XmlTransient; 027 028import org.apache.camel.CamelContextAware; 029import org.apache.camel.Expression; 030import org.apache.camel.Processor; 031import org.apache.camel.builder.ProcessClause; 032import org.apache.camel.model.language.ExpressionDefinition; 033import org.apache.camel.processor.EvaluateExpressionProcessor; 034import org.apache.camel.processor.Pipeline; 035import org.apache.camel.processor.RecipientList; 036import org.apache.camel.processor.aggregate.AggregationStrategy; 037import org.apache.camel.processor.aggregate.AggregationStrategyBeanAdapter; 038import org.apache.camel.processor.aggregate.ShareUnitOfWorkAggregationStrategy; 039import org.apache.camel.processor.aggregate.UseLatestAggregationStrategy; 040import org.apache.camel.spi.Metadata; 041import org.apache.camel.spi.RouteContext; 042import org.apache.camel.util.CamelContextHelper; 043 044/** 045 * Routes messages to a number of dynamically specified recipients (dynamic to) 046 * 047 * @version 048 */ 049@Metadata(label = "eip,endpoint,routing") 050@XmlRootElement(name = "recipientList") 051@XmlAccessorType(XmlAccessType.FIELD) 052public class RecipientListDefinition<Type extends ProcessorDefinition<Type>> extends NoOutputExpressionNode implements ExecutorServiceAwareDefinition<RecipientListDefinition<Type>> { 053 @XmlTransient 054 private AggregationStrategy aggregationStrategy; 055 @XmlTransient 056 private ExecutorService executorService; 057 @XmlAttribute @Metadata(defaultValue = ",") 058 private String delimiter; 059 @XmlAttribute 060 private Boolean parallelProcessing; 061 @XmlAttribute 062 private String strategyRef; 063 @XmlAttribute 064 private String strategyMethodName; 065 @XmlAttribute 066 private Boolean strategyMethodAllowNull; 067 @XmlAttribute 068 private String executorServiceRef; 069 @XmlAttribute 070 private Boolean stopOnException; 071 @XmlAttribute 072 private Boolean ignoreInvalidEndpoints; 073 @XmlAttribute 074 private Boolean streaming; 075 @XmlAttribute @Metadata(defaultValue = "0") 076 private Long timeout; 077 @XmlAttribute 078 private String onPrepareRef; 079 @XmlTransient 080 private Processor onPrepare; 081 @XmlAttribute 082 private Boolean shareUnitOfWork; 083 @XmlAttribute 084 private Integer cacheSize; 085 @XmlAttribute 086 private Boolean parallelAggregate; 087 @XmlAttribute 088 private Boolean stopOnAggregateException; 089 090 public RecipientListDefinition() { 091 } 092 093 public RecipientListDefinition(ExpressionDefinition expression) { 094 super(expression); 095 } 096 097 public RecipientListDefinition(Expression expression) { 098 super(expression); 099 } 100 101 @Override 102 public String toString() { 103 return "RecipientList[" + getExpression() + "]"; 104 } 105 106 @Override 107 public String getLabel() { 108 return "recipientList[" + getExpression() + "]"; 109 } 110 111 @Override 112 public Processor createProcessor(RouteContext routeContext) throws Exception { 113 final Expression expression = getExpression().createExpression(routeContext); 114 115 boolean isParallelProcessing = getParallelProcessing() != null && getParallelProcessing(); 116 boolean isStreaming = getStreaming() != null && getStreaming(); 117 boolean isParallelAggregate = getParallelAggregate() != null && getParallelAggregate(); 118 boolean isShareUnitOfWork = getShareUnitOfWork() != null && getShareUnitOfWork(); 119 boolean isStopOnException = getStopOnException() != null && getStopOnException(); 120 boolean isIgnoreInvalidEndpoints = getIgnoreInvalidEndpoints() != null && getIgnoreInvalidEndpoints(); 121 boolean isStopOnAggregateException = getStopOnAggregateException() != null && getStopOnAggregateException(); 122 123 RecipientList answer; 124 if (delimiter != null) { 125 answer = new RecipientList(routeContext.getCamelContext(), expression, delimiter); 126 } else { 127 answer = new RecipientList(routeContext.getCamelContext(), expression); 128 } 129 answer.setAggregationStrategy(createAggregationStrategy(routeContext)); 130 answer.setParallelProcessing(isParallelProcessing); 131 answer.setParallelAggregate(isParallelAggregate); 132 answer.setStreaming(isStreaming); 133 answer.setShareUnitOfWork(isShareUnitOfWork); 134 answer.setStopOnException(isStopOnException); 135 answer.setIgnoreInvalidEndpoints(isIgnoreInvalidEndpoints); 136 answer.setStopOnAggregateException(isStopOnAggregateException); 137 if (getCacheSize() != null) { 138 answer.setCacheSize(getCacheSize()); 139 } 140 if (onPrepareRef != null) { 141 onPrepare = CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), onPrepareRef, Processor.class); 142 } 143 if (onPrepare != null) { 144 answer.setOnPrepare(onPrepare); 145 } 146 if (getTimeout() != null) { 147 answer.setTimeout(getTimeout()); 148 } 149 150 boolean shutdownThreadPool = ProcessorDefinitionHelper.willCreateNewThreadPool(routeContext, this, isParallelProcessing); 151 ExecutorService threadPool = ProcessorDefinitionHelper.getConfiguredExecutorService(routeContext, "RecipientList", this, isParallelProcessing); 152 answer.setExecutorService(threadPool); 153 answer.setShutdownExecutorService(shutdownThreadPool); 154 long timeout = getTimeout() != null ? getTimeout() : 0; 155 if (timeout > 0 && !isParallelProcessing) { 156 throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled."); 157 } 158 159 // create a pipeline with two processors 160 // the first is the eval processor which evaluates the expression to use 161 // the second is the recipient list 162 List<Processor> pipe = new ArrayList<Processor>(2); 163 164 // the eval processor must be wrapped in error handler, so in case there was an 165 // error during evaluation, the error handler can deal with it 166 // the recipient list is not in error handler, as its has its own special error handling 167 // when sending to the recipients individually 168 Processor evalProcessor = new EvaluateExpressionProcessor(expression); 169 evalProcessor = super.wrapInErrorHandler(routeContext, evalProcessor); 170 171 pipe.add(evalProcessor); 172 pipe.add(answer); 173 174 // wrap in nested pipeline so this appears as one processor 175 // (threads definition does this as well) 176 return new Pipeline(routeContext.getCamelContext(), pipe) { 177 @Override 178 public String toString() { 179 return "RecipientList[" + expression + "]"; 180 } 181 }; 182 } 183 184 private AggregationStrategy createAggregationStrategy(RouteContext routeContext) { 185 AggregationStrategy strategy = getAggregationStrategy(); 186 if (strategy == null && strategyRef != null) { 187 Object aggStrategy = routeContext.lookup(strategyRef, Object.class); 188 if (aggStrategy instanceof AggregationStrategy) { 189 strategy = (AggregationStrategy) aggStrategy; 190 } else if (aggStrategy != null) { 191 AggregationStrategyBeanAdapter adapter = new AggregationStrategyBeanAdapter(aggStrategy, getStrategyMethodName()); 192 if (getStrategyMethodAllowNull() != null) { 193 adapter.setAllowNullNewExchange(getStrategyMethodAllowNull()); 194 adapter.setAllowNullOldExchange(getStrategyMethodAllowNull()); 195 } 196 strategy = adapter; 197 } else { 198 throw new IllegalArgumentException("Cannot find AggregationStrategy in Registry with name: " + strategyRef); 199 } 200 } 201 202 if (strategy == null) { 203 // default to use latest aggregation strategy 204 strategy = new UseLatestAggregationStrategy(); 205 } 206 207 if (strategy instanceof CamelContextAware) { 208 ((CamelContextAware) strategy).setCamelContext(routeContext.getCamelContext()); 209 } 210 211 if (shareUnitOfWork != null && shareUnitOfWork) { 212 // wrap strategy in share unit of work 213 strategy = new ShareUnitOfWorkAggregationStrategy(strategy); 214 } 215 216 return strategy; 217 } 218 219 // Fluent API 220 // ------------------------------------------------------------------------- 221 222 @Override 223 @SuppressWarnings("unchecked") 224 public Type end() { 225 // allow end() to return to previous type so you can continue in the DSL 226 return (Type) super.end(); 227 } 228 229 /** 230 * Delimiter used if the Expression returned multiple endpoints. Can be turned off using the value <tt>false</tt>. 231 * <p/> 232 * The default value is , 233 * 234 * @param delimiter the delimiter 235 * @return the builder 236 */ 237 public RecipientListDefinition<Type> delimiter(String delimiter) { 238 setDelimiter(delimiter); 239 return this; 240 } 241 242 /** 243 * Sets the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList. 244 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 245 */ 246 public RecipientListDefinition<Type> aggregationStrategy(AggregationStrategy aggregationStrategy) { 247 setAggregationStrategy(aggregationStrategy); 248 return this; 249 } 250 251 /** 252 * Sets a reference to the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList. 253 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 254 */ 255 public RecipientListDefinition<Type> aggregationStrategyRef(String aggregationStrategyRef) { 256 setStrategyRef(aggregationStrategyRef); 257 return this; 258 } 259 260 /** 261 * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy. 262 * 263 * @param methodName the method name to call 264 * @return the builder 265 */ 266 public RecipientListDefinition<Type> aggregationStrategyMethodName(String methodName) { 267 setStrategyMethodName(methodName); 268 return this; 269 } 270 271 /** 272 * If this option is false then the aggregate method is not used if there was no data to enrich. 273 * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy 274 * 275 * @return the builder 276 */ 277 public RecipientListDefinition<Type> aggregationStrategyMethodAllowNull() { 278 setStrategyMethodAllowNull(true); 279 return this; 280 } 281 282 /** 283 * Ignore the invalidate endpoint exception when try to create a producer with that endpoint 284 * 285 * @return the builder 286 */ 287 public RecipientListDefinition<Type> ignoreInvalidEndpoints() { 288 setIgnoreInvalidEndpoints(true); 289 return this; 290 } 291 292 /** 293 * If enabled then sending messages to the recipients occurs concurrently. 294 * Note the caller thread will still wait until all messages has been fully processed, before it continues. 295 * Its only the sending and processing the replies from the recipients which happens concurrently. 296 * 297 * @return the builder 298 */ 299 public RecipientListDefinition<Type> parallelProcessing() { 300 setParallelProcessing(true); 301 return this; 302 } 303 304 /** 305 * If enabled then sending messages to the recipients occurs concurrently. 306 * Note the caller thread will still wait until all messages has been fully processed, before it continues. 307 * Its only the sending and processing the replies from the recipients which happens concurrently. 308 * 309 * @return the builder 310 */ 311 public RecipientListDefinition<Type> parallelProcessing(boolean parallelProcessing) { 312 setParallelProcessing(parallelProcessing); 313 return this; 314 } 315 316 /** 317 * If enabled then the aggregate method on AggregationStrategy can be called concurrently. 318 * Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe. 319 * By default this is false meaning that Camel synchronizes the call to the aggregate method. 320 * Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe. 321 * 322 * @return the builder 323 */ 324 public RecipientListDefinition<Type> parallelAggregate() { 325 setParallelAggregate(true); 326 return this; 327 } 328 329 /** 330 * If enabled, unwind exceptions occurring at aggregation time to the error handler when parallelProcessing is used. 331 * Currently, aggregation time exceptions do not stop the route processing when parallelProcessing is used. 332 * Enabling this option allows to work around this behavior. 333 * 334 * The default value is <code>false</code> for the sake of backward compatibility. 335 * 336 * @return the builder 337 */ 338 public RecipientListDefinition<Type> stopOnAggregateException() { 339 setStopOnAggregateException(true); 340 return this; 341 } 342 343 /** 344 * If enabled then Camel will process replies out-of-order, eg in the order they come back. 345 * If disabled, Camel will process replies in the same order as defined by the recipient list. 346 * 347 * @return the builder 348 */ 349 public RecipientListDefinition<Type> streaming() { 350 setStreaming(true); 351 return this; 352 } 353 354 /** 355 * Will now stop further processing if an exception or failure occurred during processing of an 356 * {@link org.apache.camel.Exchange} and the caused exception will be thrown. 357 * <p/> 358 * Will also stop if processing the exchange failed (has a fault message) or an exception 359 * was thrown and handled by the error handler (such as using onException). In all situations 360 * the recipient list will stop further processing. This is the same behavior as in pipeline, which 361 * is used by the routing engine. 362 * <p/> 363 * The default behavior is to <b>not</b> stop but continue processing till the end 364 * 365 * @return the builder 366 */ 367 public RecipientListDefinition<Type> stopOnException() { 368 setStopOnException(true); 369 return this; 370 } 371 372 /** 373 * To use a custom Thread Pool to be used for parallel processing. 374 * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well. 375 */ 376 public RecipientListDefinition<Type> executorService(ExecutorService executorService) { 377 setExecutorService(executorService); 378 return this; 379 } 380 381 /** 382 * Refers to a custom Thread Pool to be used for parallel processing. 383 * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well. 384 */ 385 public RecipientListDefinition<Type> executorServiceRef(String executorServiceRef) { 386 setExecutorServiceRef(executorServiceRef); 387 return this; 388 } 389 390 /** 391 * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be used send. 392 * This can be used to deep-clone messages that should be send, or any custom logic needed before 393 * the exchange is send. 394 * 395 * @param onPrepare the processor 396 * @return the builder 397 */ 398 public RecipientListDefinition<Type> onPrepare(Processor onPrepare) { 399 setOnPrepare(onPrepare); 400 return this; 401 } 402 403 /** 404 * Sets the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be used send using a fluent buidler. 405 */ 406 public ProcessClause<RecipientListDefinition<Type>> onPrepare() { 407 ProcessClause<RecipientListDefinition<Type>> clause = new ProcessClause<>(this); 408 setOnPrepare(clause); 409 return clause; 410 } 411 412 /** 413 * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send. 414 * This can be used to deep-clone messages that should be send, or any custom logic needed before 415 * the exchange is send. 416 * 417 * @param onPrepareRef reference to the processor to lookup in the {@link org.apache.camel.spi.Registry} 418 * @return the builder 419 */ 420 public RecipientListDefinition<Type> onPrepareRef(String onPrepareRef) { 421 setOnPrepareRef(onPrepareRef); 422 return this; 423 } 424 425 /** 426 * Sets a total timeout specified in millis, when using parallel processing. 427 * If the Recipient List hasn't been able to send and process all replies within the given timeframe, 428 * then the timeout triggers and the Recipient List breaks out and continues. 429 * Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out. 430 * If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel 431 * to shut down in a graceful manner may continue to run. So use this option with a bit of care. 432 * 433 * @param timeout timeout in millis 434 * @return the builder 435 */ 436 public RecipientListDefinition<Type> timeout(long timeout) { 437 setTimeout(timeout); 438 return this; 439 } 440 441 /** 442 * Shares the {@link org.apache.camel.spi.UnitOfWork} with the parent and each of the sub messages. 443 * Recipient List will by default not share unit of work between the parent exchange and each recipient exchange. 444 * This means each sub exchange has its own individual unit of work. 445 * 446 * @return the builder. 447 * @see org.apache.camel.spi.SubUnitOfWork 448 */ 449 public RecipientListDefinition<Type> shareUnitOfWork() { 450 setShareUnitOfWork(true); 451 return this; 452 } 453 454 /** 455 * Sets the maximum size used by the {@link org.apache.camel.impl.ProducerCache} which is used 456 * to cache and reuse producers when using this recipient list, when uris are reused. 457 * 458 * @param cacheSize the cache size, use <tt>0</tt> for default cache size, or <tt>-1</tt> to turn cache off. 459 * @return the builder 460 */ 461 public RecipientListDefinition<Type> cacheSize(int cacheSize) { 462 setCacheSize(cacheSize); 463 return this; 464 } 465 466 // Properties 467 //------------------------------------------------------------------------- 468 469 470 /** 471 * Expression that returns which endpoints (url) to send the message to (the recipients). 472 * If the expression return an empty value then the message is not sent to any recipients. 473 */ 474 @Override 475 public void setExpression(ExpressionDefinition expression) { 476 // override to include javadoc what the expression is used for 477 super.setExpression(expression); 478 } 479 480 public String getDelimiter() { 481 return delimiter; 482 } 483 484 public void setDelimiter(String delimiter) { 485 this.delimiter = delimiter; 486 } 487 488 public Boolean getParallelProcessing() { 489 return parallelProcessing; 490 } 491 492 public void setParallelProcessing(Boolean parallelProcessing) { 493 this.parallelProcessing = parallelProcessing; 494 } 495 496 public String getStrategyRef() { 497 return strategyRef; 498 } 499 500 /** 501 * Sets a reference to the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList. 502 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 503 */ 504 public void setStrategyRef(String strategyRef) { 505 this.strategyRef = strategyRef; 506 } 507 508 public String getStrategyMethodName() { 509 return strategyMethodName; 510 } 511 512 /** 513 * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy. 514 */ 515 public void setStrategyMethodName(String strategyMethodName) { 516 this.strategyMethodName = strategyMethodName; 517 } 518 519 public Boolean getStrategyMethodAllowNull() { 520 return strategyMethodAllowNull; 521 } 522 523 /** 524 * If this option is false then the aggregate method is not used if there was no data to enrich. 525 * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy 526 */ 527 public void setStrategyMethodAllowNull(Boolean strategyMethodAllowNull) { 528 this.strategyMethodAllowNull = strategyMethodAllowNull; 529 } 530 531 public String getExecutorServiceRef() { 532 return executorServiceRef; 533 } 534 535 public void setExecutorServiceRef(String executorServiceRef) { 536 this.executorServiceRef = executorServiceRef; 537 } 538 539 public Boolean getIgnoreInvalidEndpoints() { 540 return ignoreInvalidEndpoints; 541 } 542 543 public void setIgnoreInvalidEndpoints(Boolean ignoreInvalidEndpoints) { 544 this.ignoreInvalidEndpoints = ignoreInvalidEndpoints; 545 } 546 547 public Boolean getStopOnException() { 548 return stopOnException; 549 } 550 551 public void setStopOnException(Boolean stopOnException) { 552 this.stopOnException = stopOnException; 553 } 554 555 public AggregationStrategy getAggregationStrategy() { 556 return aggregationStrategy; 557 } 558 559 /** 560 * Sets the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList. 561 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 562 */ 563 public void setAggregationStrategy(AggregationStrategy aggregationStrategy) { 564 this.aggregationStrategy = aggregationStrategy; 565 } 566 567 public ExecutorService getExecutorService() { 568 return executorService; 569 } 570 571 public void setExecutorService(ExecutorService executorService) { 572 this.executorService = executorService; 573 } 574 575 public Boolean getStreaming() { 576 return streaming; 577 } 578 579 public void setStreaming(Boolean streaming) { 580 this.streaming = streaming; 581 } 582 583 public Long getTimeout() { 584 return timeout; 585 } 586 587 public void setTimeout(Long timeout) { 588 this.timeout = timeout; 589 } 590 591 public String getOnPrepareRef() { 592 return onPrepareRef; 593 } 594 595 public void setOnPrepareRef(String onPrepareRef) { 596 this.onPrepareRef = onPrepareRef; 597 } 598 599 public Processor getOnPrepare() { 600 return onPrepare; 601 } 602 603 public void setOnPrepare(Processor onPrepare) { 604 this.onPrepare = onPrepare; 605 } 606 607 public Boolean getShareUnitOfWork() { 608 return shareUnitOfWork; 609 } 610 611 public void setShareUnitOfWork(Boolean shareUnitOfWork) { 612 this.shareUnitOfWork = shareUnitOfWork; 613 } 614 615 public Integer getCacheSize() { 616 return cacheSize; 617 } 618 619 public void setCacheSize(Integer cacheSize) { 620 this.cacheSize = cacheSize; 621 } 622 623 public Boolean getParallelAggregate() { 624 return parallelAggregate; 625 } 626 627 public void setParallelAggregate(Boolean parallelAggregate) { 628 this.parallelAggregate = parallelAggregate; 629 } 630 631 public Boolean getStopOnAggregateException() { 632 return stopOnAggregateException; 633 } 634 635 public void setStopOnAggregateException(Boolean stopOnAggregateException) { 636 this.stopOnAggregateException = stopOnAggregateException; 637 } 638}