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 javax.xml.bind.annotation.XmlAccessType; 022import javax.xml.bind.annotation.XmlAccessorType; 023import javax.xml.bind.annotation.XmlElement; 024import javax.xml.bind.annotation.XmlElementRef; 025import javax.xml.bind.annotation.XmlElements; 026import javax.xml.bind.annotation.XmlRootElement; 027import javax.xml.bind.annotation.XmlTransient; 028 029import org.apache.camel.Expression; 030import org.apache.camel.Processor; 031import org.apache.camel.model.config.BatchResequencerConfig; 032import org.apache.camel.model.config.ResequencerConfig; 033import org.apache.camel.model.config.StreamResequencerConfig; 034import org.apache.camel.model.language.ExpressionDefinition; 035import org.apache.camel.processor.CamelInternalProcessor; 036import org.apache.camel.processor.Resequencer; 037import org.apache.camel.processor.StreamResequencer; 038import org.apache.camel.processor.resequencer.ExpressionResultComparator; 039import org.apache.camel.spi.Metadata; 040import org.apache.camel.spi.RouteContext; 041import org.apache.camel.util.CamelContextHelper; 042import org.apache.camel.util.ObjectHelper; 043 044/** 045 * Resequences (re-order) messages based on an expression 046 * 047 * @version 048 */ 049@Metadata(label = "eip,routing") 050@XmlRootElement(name = "resequence") 051@XmlAccessorType(XmlAccessType.FIELD) 052public class ResequenceDefinition extends ProcessorDefinition<ResequenceDefinition> { 053 @Metadata(required = "false") 054 @XmlElements({ 055 @XmlElement(name = "batch-config", type = BatchResequencerConfig.class), 056 @XmlElement(name = "stream-config", type = StreamResequencerConfig.class)} 057 ) 058 private ResequencerConfig resequencerConfig; 059 @XmlTransient 060 private BatchResequencerConfig batchConfig; 061 @XmlTransient 062 private StreamResequencerConfig streamConfig; 063 @XmlElementRef @Metadata(required = "true") 064 private ExpressionDefinition expression; 065 @XmlElementRef 066 private List<ProcessorDefinition<?>> outputs = new ArrayList<>(); 067 068 public ResequenceDefinition() { 069 } 070 071 public ResequenceDefinition(Expression expression) { 072 if (expression != null) { 073 setExpression(ExpressionNodeHelper.toExpressionDefinition(expression)); 074 } 075 } 076 077 public List<ProcessorDefinition<?>> getOutputs() { 078 return outputs; 079 } 080 081 public void setOutputs(List<ProcessorDefinition<?>> outputs) { 082 this.outputs = outputs; 083 } 084 085 @Override 086 public boolean isOutputSupported() { 087 return true; 088 } 089 090 // Fluent API 091 // ------------------------------------------------------------------------- 092 /** 093 * Configures the stream-based resequencing algorithm using the default 094 * configuration. 095 * 096 * @return the builder 097 */ 098 public ResequenceDefinition stream() { 099 return stream(StreamResequencerConfig.getDefault()); 100 } 101 102 /** 103 * Configures the batch-based resequencing algorithm using the default 104 * configuration. 105 * 106 * @return the builder 107 */ 108 public ResequenceDefinition batch() { 109 return batch(BatchResequencerConfig.getDefault()); 110 } 111 112 /** 113 * Configures the stream-based resequencing algorithm using the given 114 * {@link StreamResequencerConfig}. 115 * 116 * @param config the config 117 * @return the builder 118 */ 119 public ResequenceDefinition stream(StreamResequencerConfig config) { 120 this.streamConfig = config; 121 this.batchConfig = null; 122 return this; 123 } 124 125 /** 126 * Configures the batch-based resequencing algorithm using the given 127 * {@link BatchResequencerConfig}. 128 * 129 * @param config the config 130 * @return the builder 131 */ 132 public ResequenceDefinition batch(BatchResequencerConfig config) { 133 this.batchConfig = config; 134 this.streamConfig = null; 135 return this; 136 } 137 138 /** 139 * Sets the timeout 140 * @param timeout timeout in millis 141 * @return the builder 142 */ 143 public ResequenceDefinition timeout(long timeout) { 144 if (streamConfig != null) { 145 streamConfig.setTimeout(timeout); 146 } else { 147 // initialize batch mode as its default mode 148 if (batchConfig == null) { 149 batch(); 150 } 151 batchConfig.setBatchTimeout(timeout); 152 } 153 return this; 154 } 155 156 /** 157 * Sets the interval in milli seconds the stream resequencer will at most wait 158 * while waiting for condition of being able to deliver. 159 * 160 * @param deliveryAttemptInterval interval in millis 161 * @return the builder 162 */ 163 public ResequenceDefinition deliveryAttemptInterval(long deliveryAttemptInterval) { 164 if (streamConfig == null) { 165 throw new IllegalStateException("deliveryAttemptInterval() only supported for stream resequencer"); 166 } 167 streamConfig.setDeliveryAttemptInterval(deliveryAttemptInterval); 168 return this; 169 } 170 171 /** 172 * Sets the rejectOld flag to throw an error when a message older than the last delivered message is processed 173 * @return the builder 174 */ 175 public ResequenceDefinition rejectOld() { 176 if (streamConfig == null) { 177 throw new IllegalStateException("rejectOld() only supported for stream resequencer"); 178 } 179 streamConfig.setRejectOld(true); 180 return this; 181 } 182 183 /** 184 * Sets the in batch size for number of exchanges received 185 * @param batchSize the batch size 186 * @return the builder 187 */ 188 public ResequenceDefinition size(int batchSize) { 189 if (streamConfig != null) { 190 throw new IllegalStateException("size() only supported for batch resequencer"); 191 } 192 // initialize batch mode as its default mode 193 if (batchConfig == null) { 194 batch(); 195 } 196 batchConfig.setBatchSize(batchSize); 197 return this; 198 } 199 200 /** 201 * Sets the capacity for the stream resequencer 202 * 203 * @param capacity the capacity 204 * @return the builder 205 */ 206 public ResequenceDefinition capacity(int capacity) { 207 if (streamConfig == null) { 208 throw new IllegalStateException("capacity() only supported for stream resequencer"); 209 } 210 streamConfig.setCapacity(capacity); 211 return this; 212 213 } 214 215 /** 216 * Enables duplicates for the batch resequencer mode 217 * @return the builder 218 */ 219 public ResequenceDefinition allowDuplicates() { 220 if (streamConfig != null) { 221 throw new IllegalStateException("allowDuplicates() only supported for batch resequencer"); 222 } 223 // initialize batch mode as its default mode 224 if (batchConfig == null) { 225 batch(); 226 } 227 batchConfig.setAllowDuplicates(true); 228 return this; 229 } 230 231 /** 232 * Enables reverse mode for the batch resequencer mode. 233 * <p/> 234 * This means the expression for determine the sequence order will be reversed. 235 * Can be used for Z..A or 9..0 ordering. 236 * 237 * @return the builder 238 */ 239 public ResequenceDefinition reverse() { 240 if (streamConfig != null) { 241 throw new IllegalStateException("reverse() only supported for batch resequencer"); 242 } 243 // initialize batch mode as its default mode 244 if (batchConfig == null) { 245 batch(); 246 } 247 batchConfig.setReverse(true); 248 return this; 249 } 250 251 /** 252 * If an incoming {@link org.apache.camel.Exchange} is invalid, then it will be ignored. 253 * 254 * @return builder 255 */ 256 public ResequenceDefinition ignoreInvalidExchanges() { 257 if (streamConfig != null) { 258 streamConfig.setIgnoreInvalidExchanges(true); 259 } else { 260 // initialize batch mode as its default mode 261 if (batchConfig == null) { 262 batch(); 263 } 264 batchConfig.setIgnoreInvalidExchanges(true); 265 } 266 return this; 267 } 268 269 /** 270 * Sets the comparator to use for stream resequencer 271 * 272 * @param comparator the comparator 273 * @return the builder 274 */ 275 public ResequenceDefinition comparator(ExpressionResultComparator comparator) { 276 if (streamConfig == null) { 277 throw new IllegalStateException("comparator() only supported for stream resequencer"); 278 } 279 streamConfig.setComparator(comparator); 280 return this; 281 } 282 283 @Override 284 public String toString() { 285 return "Resequencer[" + getExpression() + " -> " + getOutputs() + "]"; 286 } 287 288 @Override 289 public String getLabel() { 290 return "resequencer[" + (getExpression() != null ? getExpression().getLabel() : "") + "]"; 291 } 292 293 public ResequencerConfig getResequencerConfig() { 294 return resequencerConfig; 295 } 296 297 /** 298 * To configure the resequencer in using either batch or stream configuration. Will by default use batch configuration. 299 */ 300 public void setResequencerConfig(ResequencerConfig resequencerConfig) { 301 this.resequencerConfig = resequencerConfig; 302 } 303 304 public BatchResequencerConfig getBatchConfig() { 305 if (batchConfig == null && resequencerConfig != null && resequencerConfig instanceof BatchResequencerConfig) { 306 return (BatchResequencerConfig) resequencerConfig; 307 } 308 return batchConfig; 309 } 310 311 public StreamResequencerConfig getStreamConfig() { 312 if (streamConfig == null && resequencerConfig != null && resequencerConfig instanceof StreamResequencerConfig) { 313 return (StreamResequencerConfig) resequencerConfig; 314 } 315 return streamConfig; 316 } 317 318 public void setBatchConfig(BatchResequencerConfig batchConfig) { 319 this.batchConfig = batchConfig; 320 } 321 322 public void setStreamConfig(StreamResequencerConfig streamConfig) { 323 this.streamConfig = streamConfig; 324 } 325 326 public ExpressionDefinition getExpression() { 327 return expression; 328 } 329 330 /** 331 * Expression to use for re-ordering the messages, such as a header with a sequence number 332 */ 333 public void setExpression(ExpressionDefinition expression) { 334 this.expression = expression; 335 } 336 337 @Override 338 public Processor createProcessor(RouteContext routeContext) throws Exception { 339 // if configured from XML then streamConfig has been set with the configuration 340 if (resequencerConfig != null) { 341 if (resequencerConfig instanceof StreamResequencerConfig) { 342 streamConfig = (StreamResequencerConfig) resequencerConfig; 343 } else { 344 batchConfig = (BatchResequencerConfig) resequencerConfig; 345 } 346 } 347 348 if (streamConfig != null) { 349 return createStreamResequencer(routeContext, streamConfig); 350 } else { 351 if (batchConfig == null) { 352 // default as batch mode 353 batch(); 354 } 355 return createBatchResequencer(routeContext, batchConfig); 356 } 357 } 358 359 /** 360 * Creates a batch {@link Resequencer} instance applying the given <code>config</code>. 361 * 362 * @param routeContext route context. 363 * @param config batch resequencer configuration. 364 * @return the configured batch resequencer. 365 * @throws Exception can be thrown 366 */ 367 @SuppressWarnings("deprecation") 368 protected Resequencer createBatchResequencer(RouteContext routeContext, 369 BatchResequencerConfig config) throws Exception { 370 Processor processor = this.createChildProcessor(routeContext, true); 371 Expression expression = getExpression().createExpression(routeContext); 372 373 // and wrap in unit of work 374 CamelInternalProcessor internal = new CamelInternalProcessor(processor); 375 internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(routeContext)); 376 377 ObjectHelper.notNull(config, "config", this); 378 ObjectHelper.notNull(expression, "expression", this); 379 380 boolean isReverse = config.getReverse() != null && config.getReverse(); 381 boolean isAllowDuplicates = config.getAllowDuplicates() != null && config.getAllowDuplicates(); 382 383 Resequencer resequencer = new Resequencer(routeContext.getCamelContext(), internal, expression, isAllowDuplicates, isReverse); 384 resequencer.setBatchSize(config.getBatchSize()); 385 resequencer.setBatchTimeout(config.getBatchTimeout()); 386 resequencer.setReverse(isReverse); 387 resequencer.setAllowDuplicates(isAllowDuplicates); 388 if (config.getIgnoreInvalidExchanges() != null) { 389 resequencer.setIgnoreInvalidExchanges(config.getIgnoreInvalidExchanges()); 390 } 391 return resequencer; 392 } 393 394 /** 395 * Creates a {@link StreamResequencer} instance applying the given <code>config</code>. 396 * 397 * @param routeContext route context. 398 * @param config stream resequencer configuration. 399 * @return the configured stream resequencer. 400 * @throws Exception can be thrwon 401 */ 402 protected StreamResequencer createStreamResequencer(RouteContext routeContext, 403 StreamResequencerConfig config) throws Exception { 404 Processor processor = this.createChildProcessor(routeContext, true); 405 Expression expression = getExpression().createExpression(routeContext); 406 407 CamelInternalProcessor internal = new CamelInternalProcessor(processor); 408 internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(routeContext)); 409 410 ObjectHelper.notNull(config, "config", this); 411 ObjectHelper.notNull(expression, "expression", this); 412 413 ExpressionResultComparator comparator; 414 if (config.getComparatorRef() != null) { 415 comparator = CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), config.getComparatorRef(), ExpressionResultComparator.class); 416 } else { 417 comparator = config.getComparator(); 418 } 419 comparator.setExpression(expression); 420 421 StreamResequencer resequencer = new StreamResequencer(routeContext.getCamelContext(), internal, comparator, expression); 422 resequencer.setTimeout(config.getTimeout()); 423 if (config.getDeliveryAttemptInterval() != null) { 424 resequencer.setDeliveryAttemptInterval(config.getDeliveryAttemptInterval()); 425 } 426 resequencer.setCapacity(config.getCapacity()); 427 resequencer.setRejectOld(config.getRejectOld()); 428 if (config.getIgnoreInvalidExchanges() != null) { 429 resequencer.setIgnoreInvalidExchanges(config.getIgnoreInvalidExchanges()); 430 } 431 return resequencer; 432 } 433 434}