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<ProcessorDefinition<?>>(); 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 rejectOld flag to throw an error when a message older than the last delivered message is processed 158 * @return the builder 159 */ 160 public ResequenceDefinition rejectOld() { 161 if (streamConfig == null) { 162 throw new IllegalStateException("rejectOld() only supported for stream resequencer"); 163 } 164 streamConfig.setRejectOld(true); 165 return this; 166 } 167 168 /** 169 * Sets the in batch size for number of exchanges received 170 * @param batchSize the batch size 171 * @return the builder 172 */ 173 public ResequenceDefinition size(int batchSize) { 174 if (streamConfig != null) { 175 throw new IllegalStateException("size() only supported for batch resequencer"); 176 } 177 // initialize batch mode as its default mode 178 if (batchConfig == null) { 179 batch(); 180 } 181 batchConfig.setBatchSize(batchSize); 182 return this; 183 } 184 185 /** 186 * Sets the capacity for the stream resequencer 187 * 188 * @param capacity the capacity 189 * @return the builder 190 */ 191 public ResequenceDefinition capacity(int capacity) { 192 if (streamConfig == null) { 193 throw new IllegalStateException("capacity() only supported for stream resequencer"); 194 } 195 streamConfig.setCapacity(capacity); 196 return this; 197 198 } 199 200 /** 201 * Enables duplicates for the batch resequencer mode 202 * @return the builder 203 */ 204 public ResequenceDefinition allowDuplicates() { 205 if (streamConfig != null) { 206 throw new IllegalStateException("allowDuplicates() only supported for batch resequencer"); 207 } 208 // initialize batch mode as its default mode 209 if (batchConfig == null) { 210 batch(); 211 } 212 batchConfig.setAllowDuplicates(true); 213 return this; 214 } 215 216 /** 217 * Enables reverse mode for the batch resequencer mode. 218 * <p/> 219 * This means the expression for determine the sequence order will be reversed. 220 * Can be used for Z..A or 9..0 ordering. 221 * 222 * @return the builder 223 */ 224 public ResequenceDefinition reverse() { 225 if (streamConfig != null) { 226 throw new IllegalStateException("reverse() only supported for batch resequencer"); 227 } 228 // initialize batch mode as its default mode 229 if (batchConfig == null) { 230 batch(); 231 } 232 batchConfig.setReverse(true); 233 return this; 234 } 235 236 /** 237 * If an incoming {@link org.apache.camel.Exchange} is invalid, then it will be ignored. 238 * 239 * @return builder 240 */ 241 public ResequenceDefinition ignoreInvalidExchanges() { 242 if (streamConfig != null) { 243 streamConfig.setIgnoreInvalidExchanges(true); 244 } else { 245 // initialize batch mode as its default mode 246 if (batchConfig == null) { 247 batch(); 248 } 249 batchConfig.setIgnoreInvalidExchanges(true); 250 } 251 return this; 252 } 253 254 /** 255 * Sets the comparator to use for stream resequencer 256 * 257 * @param comparator the comparator 258 * @return the builder 259 */ 260 public ResequenceDefinition comparator(ExpressionResultComparator comparator) { 261 if (streamConfig == null) { 262 throw new IllegalStateException("comparator() only supported for stream resequencer"); 263 } 264 streamConfig.setComparator(comparator); 265 return this; 266 } 267 268 @Override 269 public String toString() { 270 return "Resequencer[" + getExpression() + " -> " + getOutputs() + "]"; 271 } 272 273 @Override 274 public String getLabel() { 275 return "resequencer[" + (getExpression() != null ? getExpression().getLabel() : "") + "]"; 276 } 277 278 public ResequencerConfig getResequencerConfig() { 279 return resequencerConfig; 280 } 281 282 /** 283 * To configure the resequencer in using either batch or stream configuration. Will by default use batch configuration. 284 */ 285 public void setResequencerConfig(ResequencerConfig resequencerConfig) { 286 this.resequencerConfig = resequencerConfig; 287 } 288 289 public BatchResequencerConfig getBatchConfig() { 290 if (batchConfig == null && resequencerConfig != null && resequencerConfig instanceof BatchResequencerConfig) { 291 return (BatchResequencerConfig) resequencerConfig; 292 } 293 return batchConfig; 294 } 295 296 public StreamResequencerConfig getStreamConfig() { 297 if (streamConfig == null && resequencerConfig != null && resequencerConfig instanceof StreamResequencerConfig) { 298 return (StreamResequencerConfig) resequencerConfig; 299 } 300 return streamConfig; 301 } 302 303 public void setBatchConfig(BatchResequencerConfig batchConfig) { 304 this.batchConfig = batchConfig; 305 } 306 307 public void setStreamConfig(StreamResequencerConfig streamConfig) { 308 this.streamConfig = streamConfig; 309 } 310 311 public ExpressionDefinition getExpression() { 312 return expression; 313 } 314 315 /** 316 * Expression to use for re-ordering the messages, such as a header with a sequence number 317 */ 318 public void setExpression(ExpressionDefinition expression) { 319 this.expression = expression; 320 } 321 322 @Override 323 public Processor createProcessor(RouteContext routeContext) throws Exception { 324 // if configured from XML then streamConfig has been set with the configuration 325 if (resequencerConfig != null) { 326 if (resequencerConfig instanceof StreamResequencerConfig) { 327 streamConfig = (StreamResequencerConfig) resequencerConfig; 328 } else { 329 batchConfig = (BatchResequencerConfig) resequencerConfig; 330 } 331 } 332 333 if (streamConfig != null) { 334 return createStreamResequencer(routeContext, streamConfig); 335 } else { 336 if (batchConfig == null) { 337 // default as batch mode 338 batch(); 339 } 340 return createBatchResequencer(routeContext, batchConfig); 341 } 342 } 343 344 /** 345 * Creates a batch {@link Resequencer} instance applying the given <code>config</code>. 346 * 347 * @param routeContext route context. 348 * @param config batch resequencer configuration. 349 * @return the configured batch resequencer. 350 * @throws Exception can be thrown 351 */ 352 @SuppressWarnings("deprecation") 353 protected Resequencer createBatchResequencer(RouteContext routeContext, 354 BatchResequencerConfig config) throws Exception { 355 Processor processor = this.createChildProcessor(routeContext, true); 356 Expression expression = getExpression().createExpression(routeContext); 357 358 // and wrap in unit of work 359 CamelInternalProcessor internal = new CamelInternalProcessor(processor); 360 internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(routeContext)); 361 362 ObjectHelper.notNull(config, "config", this); 363 ObjectHelper.notNull(expression, "expression", this); 364 365 boolean isReverse = config.getReverse() != null && config.getReverse(); 366 boolean isAllowDuplicates = config.getAllowDuplicates() != null && config.getAllowDuplicates(); 367 368 Resequencer resequencer = new Resequencer(routeContext.getCamelContext(), internal, expression, isAllowDuplicates, isReverse); 369 resequencer.setBatchSize(config.getBatchSize()); 370 resequencer.setBatchTimeout(config.getBatchTimeout()); 371 resequencer.setReverse(isReverse); 372 resequencer.setAllowDuplicates(isAllowDuplicates); 373 if (config.getIgnoreInvalidExchanges() != null) { 374 resequencer.setIgnoreInvalidExchanges(config.getIgnoreInvalidExchanges()); 375 } 376 return resequencer; 377 } 378 379 /** 380 * Creates a {@link StreamResequencer} instance applying the given <code>config</code>. 381 * 382 * @param routeContext route context. 383 * @param config stream resequencer configuration. 384 * @return the configured stream resequencer. 385 * @throws Exception can be thrwon 386 */ 387 protected StreamResequencer createStreamResequencer(RouteContext routeContext, 388 StreamResequencerConfig config) throws Exception { 389 Processor processor = this.createChildProcessor(routeContext, true); 390 Expression expression = getExpression().createExpression(routeContext); 391 392 CamelInternalProcessor internal = new CamelInternalProcessor(processor); 393 internal.addAdvice(new CamelInternalProcessor.UnitOfWorkProcessorAdvice(routeContext)); 394 395 ObjectHelper.notNull(config, "config", this); 396 ObjectHelper.notNull(expression, "expression", this); 397 398 ExpressionResultComparator comparator; 399 if (config.getComparatorRef() != null) { 400 comparator = CamelContextHelper.mandatoryLookup(routeContext.getCamelContext(), config.getComparatorRef(), ExpressionResultComparator.class); 401 } else { 402 comparator = config.getComparator(); 403 } 404 comparator.setExpression(expression); 405 406 StreamResequencer resequencer = new StreamResequencer(routeContext.getCamelContext(), internal, comparator, expression); 407 resequencer.setTimeout(config.getTimeout()); 408 resequencer.setCapacity(config.getCapacity()); 409 resequencer.setRejectOld(config.getRejectOld()); 410 if (config.getIgnoreInvalidExchanges() != null) { 411 resequencer.setIgnoreInvalidExchanges(config.getIgnoreInvalidExchanges()); 412 } 413 return resequencer; 414 } 415 416}