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