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