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.Processor; 030import org.apache.camel.builder.AggregationStrategyClause; 031import org.apache.camel.builder.ProcessClause; 032import org.apache.camel.processor.MulticastProcessor; 033import org.apache.camel.processor.aggregate.AggregationStrategy; 034import org.apache.camel.processor.aggregate.AggregationStrategyBeanAdapter; 035import org.apache.camel.processor.aggregate.ShareUnitOfWorkAggregationStrategy; 036import org.apache.camel.processor.aggregate.UseLatestAggregationStrategy; 037import org.apache.camel.spi.Metadata; 038import org.apache.camel.spi.RouteContext; 039import org.apache.camel.util.CamelContextHelper; 040 041/** 042 * Routes the same message to multiple paths either sequentially or in parallel. 043 * 044 * @version 045 */ 046@Metadata(label = "eip,routing") 047@XmlRootElement(name = "multicast") 048@XmlAccessorType(XmlAccessType.FIELD) 049public class MulticastDefinition extends OutputDefinition<MulticastDefinition> implements ExecutorServiceAwareDefinition<MulticastDefinition> { 050 @XmlAttribute 051 private Boolean parallelProcessing; 052 @XmlAttribute 053 private String strategyRef; 054 @XmlAttribute 055 private String strategyMethodName; 056 @XmlAttribute 057 private Boolean strategyMethodAllowNull; 058 @XmlTransient 059 private ExecutorService executorService; 060 @XmlAttribute 061 private String executorServiceRef; 062 @XmlAttribute 063 private Boolean streaming; 064 @XmlAttribute 065 private Boolean stopOnException; 066 @XmlAttribute @Metadata(defaultValue = "0") 067 private Long timeout; 068 @XmlTransient 069 private AggregationStrategy aggregationStrategy; 070 @XmlAttribute 071 private String onPrepareRef; 072 @XmlTransient 073 private Processor onPrepare; 074 @XmlAttribute 075 private Boolean shareUnitOfWork; 076 @XmlAttribute 077 private Boolean parallelAggregate; 078 @XmlAttribute 079 private Boolean stopOnAggregateException; 080 081 public MulticastDefinition() { 082 } 083 084 @Override 085 public String toString() { 086 return "Multicast[" + getOutputs() + "]"; 087 } 088 089 @Override 090 public String getLabel() { 091 return "multicast"; 092 } 093 094 @Override 095 public Processor createProcessor(RouteContext routeContext) throws Exception { 096 Processor answer = this.createChildProcessor(routeContext, true); 097 098 // force the answer as a multicast processor even if there is only one child processor in the multicast 099 if (!(answer instanceof MulticastProcessor)) { 100 List<Processor> list = new ArrayList<>(1); 101 list.add(answer); 102 answer = createCompositeProcessor(routeContext, list); 103 } 104 return answer; 105 } 106 107 // Fluent API 108 // ------------------------------------------------------------------------- 109 110 /** 111 * Sets the AggregationStrategy to be used to assemble the replies from the multicasts, into a single outgoing message from the Multicast using a fluent builder. 112 */ 113 public AggregationStrategyClause<MulticastDefinition> aggregationStrategy() { 114 AggregationStrategyClause<MulticastDefinition> clause = new AggregationStrategyClause<>(this); 115 setAggregationStrategy(clause); 116 return clause; 117 } 118 119 /** 120 * Sets the AggregationStrategy to be used to assemble the replies from the multicasts, into a single outgoing message from the Multicast. 121 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy. 122 * If an exception is thrown from the aggregate method in the AggregationStrategy, then by default, that exception 123 * is not handled by the error handler. The error handler can be enabled to react if enabling the shareUnitOfWork option. 124 */ 125 public MulticastDefinition aggregationStrategy(AggregationStrategy aggregationStrategy) { 126 setAggregationStrategy(aggregationStrategy); 127 return this; 128 } 129 130 /** 131 * Sets a reference to the AggregationStrategy to be used to assemble the replies from the multicasts, into a single outgoing message from the Multicast. 132 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 133 * If an exception is thrown from the aggregate method in the AggregationStrategy, then by default, that exception 134 * is not handled by the error handler. The error handler can be enabled to react if enabling the shareUnitOfWork option. 135 */ 136 public MulticastDefinition aggregationStrategyRef(String aggregationStrategyRef) { 137 setStrategyRef(aggregationStrategyRef); 138 return this; 139 } 140 141 /** 142 * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy. 143 * 144 * @param methodName the method name to call 145 * @return the builder 146 */ 147 public MulticastDefinition aggregationStrategyMethodName(String methodName) { 148 setStrategyMethodName(methodName); 149 return this; 150 } 151 152 /** 153 * If this option is false then the aggregate method is not used if there was no data to enrich. 154 * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy 155 * 156 * @return the builder 157 */ 158 public MulticastDefinition aggregationStrategyMethodAllowNull() { 159 setStrategyMethodAllowNull(true); 160 return this; 161 } 162 163 /** 164 * If enabled then sending messages to the multicasts occurs concurrently. 165 * Note the caller thread will still wait until all messages has been fully processed, before it continues. 166 * Its only the sending and processing the replies from the multicasts which happens concurrently. 167 * 168 * @return the builder 169 */ 170 public MulticastDefinition parallelProcessing() { 171 setParallelProcessing(true); 172 return this; 173 } 174 175 /** 176 * If enabled then sending messages to the multicasts occurs concurrently. 177 * Note the caller thread will still wait until all messages has been fully processed, before it continues. 178 * Its only the sending and processing the replies from the multicasts which happens concurrently. 179 * 180 * @return the builder 181 */ 182 public MulticastDefinition parallelProcessing(boolean parallelProcessing) { 183 setParallelProcessing(parallelProcessing); 184 return this; 185 } 186 187 /** 188 * If enabled then the aggregate method on AggregationStrategy can be called concurrently. 189 * Notice that this would require the implementation of AggregationStrategy to be implemented as thread-safe. 190 * By default this is false meaning that Camel synchronizes the call to the aggregate method. 191 * Though in some use-cases this can be used to archive higher performance when the AggregationStrategy is implemented as thread-safe. 192 * 193 * @return the builder 194 */ 195 public MulticastDefinition parallelAggregate() { 196 setParallelAggregate(true); 197 return this; 198 } 199 200 /** 201 * If enabled, unwind exceptions occurring at aggregation time to the error handler when parallelProcessing is used. 202 * Currently, aggregation time exceptions do not stop the route processing when parallelProcessing is used. 203 * Enabling this option allows to work around this behavior. 204 * 205 * The default value is <code>false</code> for the sake of backward compatibility. 206 * 207 * @return the builder 208 */ 209 public MulticastDefinition stopOnAggregateException() { 210 setStopOnAggregateException(true); 211 return this; 212 } 213 214 /** 215 * If enabled then Camel will process replies out-of-order, eg in the order they come back. 216 * If disabled, Camel will process replies in the same order as defined by the multicast. 217 * 218 * @return the builder 219 */ 220 public MulticastDefinition streaming() { 221 setStreaming(true); 222 return this; 223 } 224 225 /** 226 * Will now stop further processing if an exception or failure occurred during processing of an 227 * {@link org.apache.camel.Exchange} and the caused exception will be thrown. 228 * <p/> 229 * Will also stop if processing the exchange failed (has a fault message) or an exception 230 * was thrown and handled by the error handler (such as using onException). In all situations 231 * the multicast will stop further processing. This is the same behavior as in pipeline, which 232 * is used by the routing engine. 233 * <p/> 234 * The default behavior is to <b>not</b> stop but continue processing till the end 235 * 236 * @return the builder 237 */ 238 public MulticastDefinition stopOnException() { 239 setStopOnException(true); 240 return this; 241 } 242 243 /** 244 * To use a custom Thread Pool to be used for parallel processing. 245 * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well. 246 */ 247 public MulticastDefinition executorService(ExecutorService executorService) { 248 setExecutorService(executorService); 249 return this; 250 } 251 252 /** 253 * Refers to a custom Thread Pool to be used for parallel processing. 254 * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well. 255 */ 256 public MulticastDefinition executorServiceRef(String executorServiceRef) { 257 setExecutorServiceRef(executorServiceRef); 258 return this; 259 } 260 261 /** 262 * Set the {@link Processor} to use when preparing the {@link org.apache.camel.Exchange} to be send using a fluent builder. 263 */ 264 public ProcessClause<MulticastDefinition> onPrepare() { 265 ProcessClause<MulticastDefinition> clause = new ProcessClause<>(this); 266 setOnPrepare(clause); 267 return clause; 268 } 269 270 /** 271 * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send. 272 * This can be used to deep-clone messages that should be send, or any custom logic needed before 273 * the exchange is send. 274 * 275 * @param onPrepare the processor 276 * @return the builder 277 */ 278 public MulticastDefinition onPrepare(Processor onPrepare) { 279 setOnPrepare(onPrepare); 280 return this; 281 } 282 283 /** 284 * Uses the {@link Processor} when preparing the {@link org.apache.camel.Exchange} to be send. 285 * This can be used to deep-clone messages that should be send, or any custom logic needed before 286 * the exchange is send. 287 * 288 * @param onPrepareRef reference to the processor to lookup in the {@link org.apache.camel.spi.Registry} 289 * @return the builder 290 */ 291 public MulticastDefinition onPrepareRef(String onPrepareRef) { 292 setOnPrepareRef(onPrepareRef); 293 return this; 294 } 295 296 /** 297 * Sets a total timeout specified in millis, when using parallel processing. 298 * If the Multicast hasn't been able to send and process all replies within the given timeframe, 299 * then the timeout triggers and the Multicast breaks out and continues. 300 * Notice if you provide a TimeoutAwareAggregationStrategy then the timeout method is invoked before breaking out. 301 * If the timeout is reached with running tasks still remaining, certain tasks for which it is difficult for Camel 302 * to shut down in a graceful manner may continue to run. So use this option with a bit of care. 303 * 304 * @param timeout timeout in millis 305 * @return the builder 306 */ 307 public MulticastDefinition timeout(long timeout) { 308 setTimeout(timeout); 309 return this; 310 } 311 312 /** 313 * Shares the {@link org.apache.camel.spi.UnitOfWork} with the parent and each of the sub messages. 314 * Multicast will by default not share unit of work between the parent exchange and each multicasted exchange. 315 * This means each sub exchange has its own individual unit of work. 316 * 317 * @return the builder. 318 * @see org.apache.camel.spi.SubUnitOfWork 319 */ 320 public MulticastDefinition shareUnitOfWork() { 321 setShareUnitOfWork(true); 322 return this; 323 } 324 325 protected Processor createCompositeProcessor(RouteContext routeContext, List<Processor> list) throws Exception { 326 final AggregationStrategy strategy = createAggregationStrategy(routeContext); 327 328 boolean isParallelProcessing = getParallelProcessing() != null && getParallelProcessing(); 329 boolean isShareUnitOfWork = getShareUnitOfWork() != null && getShareUnitOfWork(); 330 boolean isStreaming = getStreaming() != null && getStreaming(); 331 boolean isStopOnException = getStopOnException() != null && getStopOnException(); 332 boolean isParallelAggregate = getParallelAggregate() != null && getParallelAggregate(); 333 boolean isStopOnAggregateException = getStopOnAggregateException() != null && getStopOnAggregateException(); 334 335 boolean shutdownThreadPool = ProcessorDefinitionHelper.willCreateNewThreadPool(routeContext, this, isParallelProcessing); 336 ExecutorService threadPool = ProcessorDefinitionHelper.getConfiguredExecutorService(routeContext, "Multicast", this, isParallelProcessing); 337 338 long timeout = getTimeout() != null ? getTimeout() : 0; 339 if (timeout > 0 && !isParallelProcessing) { 340 throw new IllegalArgumentException("Timeout is used but ParallelProcessing has not been enabled."); 341 } 342 if (onPrepareRef != null) { 343 onPrepare = CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), onPrepareRef, Processor.class); 344 } 345 346 MulticastProcessor answer = new MulticastProcessor(routeContext.getCamelContext(), list, strategy, isParallelProcessing, 347 threadPool, shutdownThreadPool, isStreaming, isStopOnException, timeout, onPrepare, isShareUnitOfWork, isParallelAggregate, isStopOnAggregateException); 348 return answer; 349 } 350 351 private AggregationStrategy createAggregationStrategy(RouteContext routeContext) { 352 AggregationStrategy strategy = getAggregationStrategy(); 353 if (strategy == null && strategyRef != null) { 354 Object aggStrategy = routeContext.lookup(strategyRef, Object.class); 355 if (aggStrategy instanceof AggregationStrategy) { 356 strategy = (AggregationStrategy) aggStrategy; 357 } else if (aggStrategy != null) { 358 AggregationStrategyBeanAdapter adapter = new AggregationStrategyBeanAdapter(aggStrategy, getStrategyMethodName()); 359 if (getStrategyMethodAllowNull() != null) { 360 adapter.setAllowNullNewExchange(getStrategyMethodAllowNull()); 361 adapter.setAllowNullOldExchange(getStrategyMethodAllowNull()); 362 } 363 strategy = adapter; 364 } else { 365 throw new IllegalArgumentException("Cannot find AggregationStrategy in Registry with name: " + strategyRef); 366 } 367 } 368 369 if (strategy == null) { 370 // default to use latest aggregation strategy 371 strategy = new UseLatestAggregationStrategy(); 372 } 373 374 if (strategy instanceof CamelContextAware) { 375 ((CamelContextAware) strategy).setCamelContext(routeContext.getCamelContext()); 376 } 377 378 if (shareUnitOfWork != null && shareUnitOfWork) { 379 // wrap strategy in share unit of work 380 strategy = new ShareUnitOfWorkAggregationStrategy(strategy); 381 } 382 383 return strategy; 384 } 385 386 public AggregationStrategy getAggregationStrategy() { 387 return aggregationStrategy; 388 } 389 390 public MulticastDefinition setAggregationStrategy(AggregationStrategy aggregationStrategy) { 391 this.aggregationStrategy = aggregationStrategy; 392 return this; 393 } 394 395 public Boolean getParallelProcessing() { 396 return parallelProcessing; 397 } 398 399 public void setParallelProcessing(Boolean parallelProcessing) { 400 this.parallelProcessing = parallelProcessing; 401 } 402 403 public Boolean getStreaming() { 404 return streaming; 405 } 406 407 public void setStreaming(Boolean streaming) { 408 this.streaming = streaming; 409 } 410 411 public Boolean getStopOnException() { 412 return stopOnException; 413 } 414 415 public void setStopOnException(Boolean stopOnException) { 416 this.stopOnException = stopOnException; 417 } 418 419 public ExecutorService getExecutorService() { 420 return executorService; 421 } 422 423 public void setExecutorService(ExecutorService executorService) { 424 this.executorService = executorService; 425 } 426 427 public String getStrategyRef() { 428 return strategyRef; 429 } 430 431 /** 432 * Refers to an AggregationStrategy to be used to assemble the replies from the multicasts, into a single outgoing message from the Multicast. 433 * By default Camel will use the last reply as the outgoing message. You can also use a POJO as the AggregationStrategy 434 */ 435 public void setStrategyRef(String strategyRef) { 436 this.strategyRef = strategyRef; 437 } 438 439 public String getStrategyMethodName() { 440 return strategyMethodName; 441 } 442 443 /** 444 * This option can be used to explicit declare the method name to use, when using POJOs as the AggregationStrategy. 445 */ 446 public void setStrategyMethodName(String strategyMethodName) { 447 this.strategyMethodName = strategyMethodName; 448 } 449 450 public Boolean getStrategyMethodAllowNull() { 451 return strategyMethodAllowNull; 452 } 453 454 /** 455 * If this option is false then the aggregate method is not used if there was no data to enrich. 456 * If this option is true then null values is used as the oldExchange (when no data to enrich), when using POJOs as the AggregationStrategy 457 */ 458 public void setStrategyMethodAllowNull(Boolean strategyMethodAllowNull) { 459 this.strategyMethodAllowNull = strategyMethodAllowNull; 460 } 461 462 public String getExecutorServiceRef() { 463 return executorServiceRef; 464 } 465 466 /** 467 * Refers to a custom Thread Pool to be used for parallel processing. 468 * Notice if you set this option, then parallel processing is automatic implied, and you do not have to enable that option as well. 469 */ 470 public void setExecutorServiceRef(String executorServiceRef) { 471 this.executorServiceRef = executorServiceRef; 472 } 473 474 public Long getTimeout() { 475 return timeout; 476 } 477 478 public void setTimeout(Long timeout) { 479 this.timeout = timeout; 480 } 481 482 public String getOnPrepareRef() { 483 return onPrepareRef; 484 } 485 486 public void setOnPrepareRef(String onPrepareRef) { 487 this.onPrepareRef = onPrepareRef; 488 } 489 490 public Processor getOnPrepare() { 491 return onPrepare; 492 } 493 494 public void setOnPrepare(Processor onPrepare) { 495 this.onPrepare = onPrepare; 496 } 497 498 public Boolean getShareUnitOfWork() { 499 return shareUnitOfWork; 500 } 501 502 public void setShareUnitOfWork(Boolean shareUnitOfWork) { 503 this.shareUnitOfWork = shareUnitOfWork; 504 } 505 506 public Boolean getParallelAggregate() { 507 return parallelAggregate; 508 } 509 510 public void setParallelAggregate(Boolean parallelAggregate) { 511 this.parallelAggregate = parallelAggregate; 512 } 513 514 public Boolean getStopOnAggregateException() { 515 return stopOnAggregateException; 516 } 517 518 public void setStopOnAggregateException(Boolean stopOnAggregateException) { 519 this.stopOnAggregateException = stopOnAggregateException; 520 } 521 522}