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