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