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 the aggregate method on AggregationStrategy can be called concurrently. 294 * Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe. 295 * By default this is false meaning that Camel synchronizes the call to the aggregate method. 296 * Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe. 297 * 298 * @return the builder 299 */ 300 public RecipientListDefinition<Type> parallelAggregate() { 301 setParallelAggregate(true); 302 return this; 303 } 304 305 /** 306 * If enabled then Camel will process replies out-of-order, eg in the order they come back. 307 * If disabled, Camel will process replies in the same order as defined by the recipient list. 308 * 309 * @return the builder 310 */ 311 public RecipientListDefinition<Type> streaming() { 312 setStreaming(true); 313 return this; 314 } 315 316 /** 317 * Will now stop further processing if an exception or failure occurred during processing of an 318 * {@link org.apache.camel.Exchange} and the caused exception will be thrown. 319 * <p/> 320 * Will also stop if processing the exchange failed (has a fault message) or an exception 321 * was thrown and handled by the error handler (such as using onException). In all situations 322 * the recipient list will stop further processing. This is the same behavior as in pipeline, which 323 * is used by the routing engine. 324 * <p/> 325 * The default behavior is to <b>not</b> stop but continue processing till the end 326 * 327 * @return the builder 328 */ 329 public RecipientListDefinition<Type> stopOnException() { 330 setStopOnException(true); 331 return this; 332 } 333 334 /** 335 * To use a custom Thread Pool to be used for parallel processing. 336 * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well. 337 */ 338 public RecipientListDefinition<Type> executorService(ExecutorService executorService) { 339 setExecutorService(executorService); 340 return this; 341 } 342 343 /** 344 * Refers to a custom Thread Pool to be used for parallel processing. 345 * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well. 346 */ 347 public RecipientListDefinition<Type> executorServiceRef(String executorServiceRef) { 348 setExecutorServiceRef(executorServiceRef); 349 return this; 350 } 351 352 /** 353 * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be used send. 354 * This can be used to deep-clone messages that should be send, or any custom logic needed before 355 * the exchange is send. 356 * 357 * @param onPrepare the processor 358 * @return the builder 359 */ 360 public RecipientListDefinition<Type> onPrepare(Processor onPrepare) { 361 setOnPrepare(onPrepare); 362 return this; 363 } 364 365 /** 366 * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send. 367 * This can be used to deep-clone messages that should be send, or any custom logic needed before 368 * the exchange is send. 369 * 370 * @param onPrepareRef reference to the processor to lookup in the {@link org.apache.camel.spi.Registry} 371 * @return the builder 372 */ 373 public RecipientListDefinition<Type> onPrepareRef(String onPrepareRef) { 374 setOnPrepareRef(onPrepareRef); 375 return this; 376 } 377 378 /** 379 * Sets a total timeout specified in millis, when using parallel processing. 380 * If the Recipient List hasn't been able to send and process all replies within the given timeframe, 381 * then the timeout triggers and the Recipient List breaks out and continues. 382 * Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out. 383 * If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel 384 * to shut down in a graceful manner may continue to run. So use this option with a bit of care. 385 * 386 * @param timeout timeout in millis 387 * @return the builder 388 */ 389 public RecipientListDefinition<Type> timeout(long timeout) { 390 setTimeout(timeout); 391 return this; 392 } 393 394 /** 395 * Shares the {@link org.apache.camel.spi.UnitOfWork} with the parent and each of the sub messages. 396 * Recipient List will by default not share unit of work between the parent exchange and each recipient exchange. 397 * This means each sub exchange has its own individual unit of work. 398 * 399 * @return the builder. 400 * @see org.apache.camel.spi.SubUnitOfWork 401 */ 402 public RecipientListDefinition<Type> shareUnitOfWork() { 403 setShareUnitOfWork(true); 404 return this; 405 } 406 407 /** 408 * Sets the maximum size used by the {@link org.apache.camel.impl.ProducerCache} which is used 409 * to cache and reuse producers when using this recipient list, when uris are reused. 410 * 411 * @param cacheSize the cache size, use <tt>0</tt> for default cache size, or <tt>-1</tt> to turn cache off. 412 * @return the builder 413 */ 414 public RecipientListDefinition<Type> cacheSize(int cacheSize) { 415 setCacheSize(cacheSize); 416 return this; 417 } 418 419 // Properties 420 //------------------------------------------------------------------------- 421 422 423 /** 424 * Expression that returns which endpoints (url) to send the message to (the recipients). 425 * If the expression return an empty value then the message is not sent to any recipients. 426 */ 427 @Override 428 public void setExpression(ExpressionDefinition expression) { 429 // override to include javadoc what the expression is used for 430 super.setExpression(expression); 431 } 432 433 public String getDelimiter() { 434 return delimiter; 435 } 436 437 public void setDelimiter(String delimiter) { 438 this.delimiter = delimiter; 439 } 440 441 public Boolean getParallelProcessing() { 442 return parallelProcessing; 443 } 444 445 public void setParallelProcessing(Boolean parallelProcessing) { 446 this.parallelProcessing = parallelProcessing; 447 } 448 449 public String getStrategyRef() { 450 return strategyRef; 451 } 452 453 /** 454 * Sets a reference to the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList. 455 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 456 */ 457 public void setStrategyRef(String strategyRef) { 458 this.strategyRef = strategyRef; 459 } 460 461 public String getStrategyMethodName() { 462 return strategyMethodName; 463 } 464 465 /** 466 * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy. 467 */ 468 public void setStrategyMethodName(String strategyMethodName) { 469 this.strategyMethodName = strategyMethodName; 470 } 471 472 public Boolean getStrategyMethodAllowNull() { 473 return strategyMethodAllowNull; 474 } 475 476 /** 477 * If this option is false then the aggregate method is not used if there was no data to enrich. 478 * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy 479 */ 480 public void setStrategyMethodAllowNull(Boolean strategyMethodAllowNull) { 481 this.strategyMethodAllowNull = strategyMethodAllowNull; 482 } 483 484 public String getExecutorServiceRef() { 485 return executorServiceRef; 486 } 487 488 public void setExecutorServiceRef(String executorServiceRef) { 489 this.executorServiceRef = executorServiceRef; 490 } 491 492 public Boolean getIgnoreInvalidEndpoints() { 493 return ignoreInvalidEndpoints; 494 } 495 496 public void setIgnoreInvalidEndpoints(Boolean ignoreInvalidEndpoints) { 497 this.ignoreInvalidEndpoints = ignoreInvalidEndpoints; 498 } 499 500 public Boolean getStopOnException() { 501 return stopOnException; 502 } 503 504 public void setStopOnException(Boolean stopOnException) { 505 this.stopOnException = stopOnException; 506 } 507 508 public AggregationStrategy getAggregationStrategy() { 509 return aggregationStrategy; 510 } 511 512 /** 513 * Sets the AggregationStrategy to be used to assemble the replies from the recipients, into a single outgoing message from the RecipientList. 514 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 515 */ 516 public void setAggregationStrategy(AggregationStrategy aggregationStrategy) { 517 this.aggregationStrategy = aggregationStrategy; 518 } 519 520 public ExecutorService getExecutorService() { 521 return executorService; 522 } 523 524 public void setExecutorService(ExecutorService executorService) { 525 this.executorService = executorService; 526 } 527 528 public Boolean getStreaming() { 529 return streaming; 530 } 531 532 public void setStreaming(Boolean streaming) { 533 this.streaming = streaming; 534 } 535 536 public Long getTimeout() { 537 return timeout; 538 } 539 540 public void setTimeout(Long timeout) { 541 this.timeout = timeout; 542 } 543 544 public String getOnPrepareRef() { 545 return onPrepareRef; 546 } 547 548 public void setOnPrepareRef(String onPrepareRef) { 549 this.onPrepareRef = onPrepareRef; 550 } 551 552 public Processor getOnPrepare() { 553 return onPrepare; 554 } 555 556 public void setOnPrepare(Processor onPrepare) { 557 this.onPrepare = onPrepare; 558 } 559 560 public Boolean getShareUnitOfWork() { 561 return shareUnitOfWork; 562 } 563 564 public void setShareUnitOfWork(Boolean shareUnitOfWork) { 565 this.shareUnitOfWork = shareUnitOfWork; 566 } 567 568 public Integer getCacheSize() { 569 return cacheSize; 570 } 571 572 public void setCacheSize(Integer cacheSize) { 573 this.cacheSize = cacheSize; 574 } 575 576 public Boolean getParallelAggregate() { 577 return parallelAggregate; 578 } 579 580 public void setParallelAggregate(Boolean parallelAggregate) { 581 this.parallelAggregate = parallelAggregate; 582 } 583}