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.component.file; 018 019import java.io.IOException; 020import java.lang.reflect.Method; 021import java.util.ArrayList; 022import java.util.Comparator; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import org.apache.camel.CamelContext; 030import org.apache.camel.Component; 031import org.apache.camel.Exchange; 032import org.apache.camel.Expression; 033import org.apache.camel.ExpressionIllegalSyntaxException; 034import org.apache.camel.LoggingLevel; 035import org.apache.camel.Message; 036import org.apache.camel.Predicate; 037import org.apache.camel.Processor; 038import org.apache.camel.impl.ScheduledPollEndpoint; 039import org.apache.camel.processor.idempotent.MemoryIdempotentRepository; 040import org.apache.camel.spi.BrowsableEndpoint; 041import org.apache.camel.spi.ExceptionHandler; 042import org.apache.camel.spi.FactoryFinder; 043import org.apache.camel.spi.IdempotentRepository; 044import org.apache.camel.spi.Language; 045import org.apache.camel.spi.UriParam; 046import org.apache.camel.util.FileUtil; 047import org.apache.camel.util.IOHelper; 048import org.apache.camel.util.ObjectHelper; 049import org.apache.camel.util.ServiceHelper; 050import org.apache.camel.util.StringHelper; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053 054/** 055 * Base class for file endpoints 056 */ 057public abstract class GenericFileEndpoint<T> extends ScheduledPollEndpoint implements BrowsableEndpoint { 058 059 protected static final String DEFAULT_STRATEGYFACTORY_CLASS = "org.apache.camel.component.file.strategy.GenericFileProcessStrategyFactory"; 060 protected static final int DEFAULT_IDEMPOTENT_CACHE_SIZE = 1000; 061 protected static final int DEFAULT_IN_PROGRESS_CACHE_SIZE = 50000; 062 063 protected final Logger log = LoggerFactory.getLogger(getClass()); 064 065 // common options 066 067 @UriParam(label = "advanced", defaultValue = "true") 068 protected boolean autoCreate = true; 069 @UriParam(label = "advanced", defaultValue = "" + FileUtil.BUFFER_SIZE) 070 protected int bufferSize = FileUtil.BUFFER_SIZE; 071 @UriParam 072 protected String charset; 073 @UriParam(javaType = "java.lang.String") 074 protected Expression fileName; 075 @UriParam 076 protected String doneFileName; 077 078 // producer options 079 080 @UriParam(label = "producer") 081 protected boolean flatten; 082 @UriParam(label = "producer", defaultValue = "Override") 083 protected GenericFileExist fileExist = GenericFileExist.Override; 084 @UriParam(label = "producer") 085 protected String tempPrefix; 086 @UriParam(label = "producer", javaType = "java.lang.String") 087 protected Expression tempFileName; 088 @UriParam(label = "producer,advanced", defaultValue = "true") 089 protected boolean eagerDeleteTargetFile = true; 090 @UriParam(label = "producer,advanced") 091 protected boolean keepLastModified; 092 @UriParam(label = "producer,advanced") 093 protected boolean allowNullBody; 094 095 // consumer options 096 097 @UriParam 098 protected GenericFileConfiguration configuration; 099 @UriParam(label = "consumer,advanced") 100 protected GenericFileProcessStrategy<T> processStrategy; 101 @UriParam(label = "consumer,advanced") 102 protected IdempotentRepository<String> inProgressRepository = MemoryIdempotentRepository.memoryIdempotentRepository(DEFAULT_IN_PROGRESS_CACHE_SIZE); 103 @UriParam(label = "consumer,advanced") 104 protected String localWorkDirectory; 105 @UriParam(label = "consumer,advanced") 106 protected boolean startingDirectoryMustExist; 107 @UriParam(label = "consumer,advanced") 108 protected boolean directoryMustExist; 109 @UriParam(label = "consumer") 110 protected boolean noop; 111 @UriParam(label = "consumer") 112 protected boolean recursive; 113 @UriParam(label = "consumer") 114 protected boolean delete; 115 @UriParam(label = "consumer") 116 protected boolean preSort; 117 @UriParam(label = "consumer,filter") 118 protected int maxMessagesPerPoll; 119 @UriParam(label = "consumer,filter", defaultValue = "true") 120 protected boolean eagerMaxMessagesPerPoll = true; 121 @UriParam(label = "consumer,filter", defaultValue = "" + Integer.MAX_VALUE) 122 protected int maxDepth = Integer.MAX_VALUE; 123 @UriParam(label = "consumer,filter") 124 protected int minDepth; 125 @UriParam(label = "consumer,filter") 126 protected String include; 127 @UriParam(label = "consumer,filter") 128 protected String exclude; 129 @UriParam(label = "consumer,filter", javaType = "java.lang.String") 130 protected Expression move; 131 @UriParam(label = "consumer", javaType = "java.lang.String") 132 protected Expression moveFailed; 133 @UriParam(label = "consumer", javaType = "java.lang.String") 134 protected Expression preMove; 135 @UriParam(label = "producer", javaType = "java.lang.String") 136 protected Expression moveExisting; 137 @UriParam(label = "consumer,filter", defaultValue = "false") 138 protected Boolean idempotent; 139 @UriParam(label = "consumer,filter", javaType = "java.lang.String") 140 protected Expression idempotentKey; 141 @UriParam(label = "consumer,filter") 142 protected IdempotentRepository<String> idempotentRepository; 143 @UriParam(label = "consumer,filter") 144 protected GenericFileFilter<T> filter; 145 @UriParam(label = "consumer,filter", javaType = "java.lang.String") 146 protected Predicate filterDirectory; 147 @UriParam(label = "consumer,filter", javaType = "java.lang.String") 148 protected Predicate filterFile; 149 @UriParam(label = "consumer,filter", defaultValue = "true") 150 protected boolean antFilterCaseSensitive = true; 151 protected volatile AntPathMatcherGenericFileFilter<T> antFilter; 152 @UriParam(label = "consumer,filter") 153 protected String antInclude; 154 @UriParam(label = "consumer,filter") 155 protected String antExclude; 156 @UriParam(label = "consumer,sort") 157 protected Comparator<GenericFile<T>> sorter; 158 @UriParam(label = "consumer,sort", javaType = "java.lang.String") 159 protected Comparator<Exchange> sortBy; 160 @UriParam(label = "consumer,sort") 161 protected boolean shuffle; 162 @UriParam(label = "consumer,lock", defaultValue = "none", enums = "none,markerFile,fileLock,rename,changed,idempotent,idempotent-changed,idempotent-rename") 163 protected String readLock = "none"; 164 @UriParam(label = "consumer,lock", defaultValue = "1000") 165 protected long readLockCheckInterval = 1000; 166 @UriParam(label = "consumer,lock", defaultValue = "10000") 167 protected long readLockTimeout = 10000; 168 @UriParam(label = "consumer,lock", defaultValue = "true") 169 protected boolean readLockMarkerFile = true; 170 @UriParam(label = "consumer,lock", defaultValue = "true") 171 protected boolean readLockDeleteOrphanLockFiles = true; 172 @UriParam(label = "consumer,lock", defaultValue = "DEBUG") 173 protected LoggingLevel readLockLoggingLevel = LoggingLevel.DEBUG; 174 @UriParam(label = "consumer,lock", defaultValue = "1") 175 protected long readLockMinLength = 1; 176 @UriParam(label = "consumer,lock", defaultValue = "0") 177 protected long readLockMinAge; 178 @UriParam(label = "consumer,lock", defaultValue = "true") 179 protected boolean readLockRemoveOnRollback = true; 180 @UriParam(label = "consumer,lock") 181 protected boolean readLockRemoveOnCommit; 182 @UriParam(label = "consumer,lock") 183 protected GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy; 184 @UriParam(label = "consumer,advanced") 185 protected ExceptionHandler onCompletionExceptionHandler; 186 187 private Pattern includePattern; 188 private Pattern excludePattern; 189 190 public GenericFileEndpoint() { 191 } 192 193 public GenericFileEndpoint(String endpointUri, Component component) { 194 super(endpointUri, component); 195 } 196 197 public boolean isSingleton() { 198 return true; 199 } 200 201 public abstract GenericFileConsumer<T> createConsumer(Processor processor) throws Exception; 202 203 public abstract GenericFileProducer<T> createProducer() throws Exception; 204 205 public abstract Exchange createExchange(GenericFile<T> file); 206 207 public abstract String getScheme(); 208 209 public abstract char getFileSeparator(); 210 211 public abstract boolean isAbsolute(String name); 212 213 /** 214 * Return the file name that will be auto-generated for the given message if 215 * none is provided 216 */ 217 public String getGeneratedFileName(Message message) { 218 return StringHelper.sanitize(message.getMessageId()); 219 } 220 221 public GenericFileProcessStrategy<T> getGenericFileProcessStrategy() { 222 if (processStrategy == null) { 223 processStrategy = createGenericFileStrategy(); 224 log.debug("Using Generic file process strategy: {}", processStrategy); 225 } 226 return processStrategy; 227 } 228 229 /** 230 * This implementation will <b>not</b> load the file content. 231 * Any file locking is neither in use by this implementation.. 232 */ 233 @Override 234 public List<Exchange> getExchanges() { 235 final List<Exchange> answer = new ArrayList<Exchange>(); 236 237 GenericFileConsumer<?> consumer = null; 238 try { 239 // create a new consumer which can poll the exchanges we want to browse 240 // do not provide a processor as we do some custom processing 241 consumer = createConsumer(null); 242 consumer.setCustomProcessor(new Processor() { 243 @Override 244 public void process(Exchange exchange) throws Exception { 245 answer.add(exchange); 246 } 247 }); 248 // do not start scheduler, as we invoke the poll manually 249 consumer.setStartScheduler(false); 250 // start consumer 251 ServiceHelper.startService(consumer); 252 // invoke poll which performs the custom processing, so we can browse the exchanges 253 consumer.poll(); 254 } catch (Exception e) { 255 throw ObjectHelper.wrapRuntimeCamelException(e); 256 } finally { 257 try { 258 ServiceHelper.stopService(consumer); 259 } catch (Exception e) { 260 log.debug("Error stopping consumer used for browsing exchanges. This exception will be ignored", e); 261 } 262 } 263 264 return answer; 265 } 266 267 /** 268 * A strategy method to lazily create the file strategy 269 */ 270 @SuppressWarnings("unchecked") 271 protected GenericFileProcessStrategy<T> createGenericFileStrategy() { 272 Class<?> factory = null; 273 try { 274 FactoryFinder finder = getCamelContext().getFactoryFinder("META-INF/services/org/apache/camel/component/"); 275 log.trace("Using FactoryFinder: {}", finder); 276 factory = finder.findClass(getScheme(), "strategy.factory.", CamelContext.class); 277 } catch (ClassNotFoundException e) { 278 log.trace("'strategy.factory.class' not found", e); 279 } catch (IOException e) { 280 log.trace("No strategy factory defined in 'META-INF/services/org/apache/camel/component/'", e); 281 } 282 283 if (factory == null) { 284 // use default 285 try { 286 log.trace("Using ClassResolver to resolve class: {}", DEFAULT_STRATEGYFACTORY_CLASS); 287 factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS); 288 } catch (Exception e) { 289 log.trace("Cannot load class: {}", DEFAULT_STRATEGYFACTORY_CLASS, e); 290 } 291 // fallback and us this class loader 292 try { 293 if (log.isTraceEnabled()) { 294 log.trace("Using classloader: {} to resolve class: {}", this.getClass().getClassLoader(), DEFAULT_STRATEGYFACTORY_CLASS); 295 } 296 factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS, this.getClass().getClassLoader()); 297 } catch (Exception e) { 298 if (log.isTraceEnabled()) { 299 log.trace("Cannot load class: {} using classloader: " + this.getClass().getClassLoader(), DEFAULT_STRATEGYFACTORY_CLASS, e); 300 } 301 } 302 303 if (factory == null) { 304 throw new TypeNotPresentException(DEFAULT_STRATEGYFACTORY_CLASS + " class not found", null); 305 } 306 } 307 308 try { 309 Method factoryMethod = factory.getMethod("createGenericFileProcessStrategy", CamelContext.class, Map.class); 310 Map<String, Object> params = getParamsAsMap(); 311 log.debug("Parameters for Generic file process strategy {}", params); 312 return (GenericFileProcessStrategy<T>) ObjectHelper.invokeMethod(factoryMethod, null, getCamelContext(), params); 313 } catch (NoSuchMethodException e) { 314 throw new TypeNotPresentException(factory.getSimpleName() + ".createGenericFileProcessStrategy method not found", e); 315 } 316 } 317 318 public boolean isNoop() { 319 return noop; 320 } 321 322 /** 323 * If true, the file is not moved or deleted in any way. 324 * This option is good for readonly data, or for ETL type requirements. 325 * If noop=true, Camel will set idempotent=true as well, to avoid consuming the same files over and over again. 326 */ 327 public void setNoop(boolean noop) { 328 this.noop = noop; 329 } 330 331 public boolean isRecursive() { 332 return recursive; 333 } 334 335 /** 336 * If a directory, will look for files in all the sub-directories as well. 337 */ 338 public void setRecursive(boolean recursive) { 339 this.recursive = recursive; 340 } 341 342 public String getInclude() { 343 return include; 344 } 345 346 /** 347 * Is used to include files, if filename matches the regex pattern (matching is case in-sensitive). 348 * <p/> 349 * Notice if you use symbols such as plus sign and others you would need to configure 350 * this using the RAW() syntax if configuring this as an endpoint uri. 351 * See more details at <a href="http://camel.apache.org/how-do-i-configure-endpoints.html">configuring endpoint uris</a> 352 */ 353 public void setInclude(String include) { 354 this.include = include; 355 this.includePattern = Pattern.compile(include, Pattern.CASE_INSENSITIVE); 356 } 357 358 public Pattern getIncludePattern() { 359 return includePattern; 360 } 361 362 public String getExclude() { 363 return exclude; 364 } 365 366 /** 367 * Is used to exclude files, if filename matches the regex pattern (matching is case in-senstive). 368 * <p/> 369 * Notice if you use symbols such as plus sign and others you would need to configure 370 * this using the RAW() syntax if configuring this as an endpoint uri. 371 * See more details at <a href="http://camel.apache.org/how-do-i-configure-endpoints.html">configuring endpoint uris</a> 372 */ 373 public void setExclude(String exclude) { 374 this.exclude = exclude; 375 this.excludePattern = Pattern.compile(exclude, Pattern.CASE_INSENSITIVE); 376 } 377 378 public Pattern getExcludePattern() { 379 return this.excludePattern; 380 } 381 382 public String getAntInclude() { 383 return antInclude; 384 } 385 386 /** 387 * Ant style filter inclusion. 388 * Multiple inclusions may be specified in comma-delimited format. 389 */ 390 public void setAntInclude(String antInclude) { 391 this.antInclude = antInclude; 392 } 393 394 public String getAntExclude() { 395 return antExclude; 396 } 397 398 /** 399 * Ant style filter exclusion. If both antInclude and antExclude are used, antExclude takes precedence over antInclude. 400 * Multiple exclusions may be specified in comma-delimited format. 401 */ 402 public void setAntExclude(String antExclude) { 403 this.antExclude = antExclude; 404 } 405 406 public boolean isAntFilterCaseSensitive() { 407 return antFilterCaseSensitive; 408 } 409 410 /** 411 * Sets case sensitive flag on ant filter 412 */ 413 public void setAntFilterCaseSensitive(boolean antFilterCaseSensitive) { 414 this.antFilterCaseSensitive = antFilterCaseSensitive; 415 } 416 417 public GenericFileFilter<T> getAntFilter() { 418 return antFilter; 419 } 420 421 public boolean isPreSort() { 422 return preSort; 423 } 424 425 /** 426 * When pre-sort is enabled then the consumer will sort the file and directory names during polling, 427 * that was retrieved from the file system. You may want to do this in case you need to operate on the files 428 * in a sorted order. The pre-sort is executed before the consumer starts to filter, and accept files 429 * to process by Camel. This option is default=false meaning disabled. 430 */ 431 public void setPreSort(boolean preSort) { 432 this.preSort = preSort; 433 } 434 435 public boolean isDelete() { 436 return delete; 437 } 438 439 /** 440 * If true, the file will be deleted after it is processed successfully. 441 */ 442 public void setDelete(boolean delete) { 443 this.delete = delete; 444 } 445 446 public boolean isFlatten() { 447 return flatten; 448 } 449 450 /** 451 * Flatten is used to flatten the file name path to strip any leading paths, so it's just the file name. 452 * This allows you to consume recursively into sub-directories, but when you eg write the files to another directory 453 * they will be written in a single directory. 454 * Setting this to true on the producer enforces that any file name in CamelFileName header 455 * will be stripped for any leading paths. 456 */ 457 public void setFlatten(boolean flatten) { 458 this.flatten = flatten; 459 } 460 461 public Expression getMove() { 462 return move; 463 } 464 465 /** 466 * Expression (such as Simple Language) used to dynamically set the filename when moving it after processing. 467 * To move files into a .done subdirectory just enter .done. 468 */ 469 public void setMove(Expression move) { 470 this.move = move; 471 } 472 473 /** 474 * @see #setMove(org.apache.camel.Expression) 475 */ 476 public void setMove(String fileLanguageExpression) { 477 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 478 this.move = createFileLanguageExpression(expression); 479 } 480 481 public Expression getMoveFailed() { 482 return moveFailed; 483 } 484 485 /** 486 * Sets the move failure expression based on Simple language. 487 * For example, to move files into a .error subdirectory use: .error. 488 * Note: When moving the files to the fail location Camel will handle the error and will not pick up the file again. 489 */ 490 public void setMoveFailed(Expression moveFailed) { 491 this.moveFailed = moveFailed; 492 } 493 494 public void setMoveFailed(String fileLanguageExpression) { 495 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 496 this.moveFailed = createFileLanguageExpression(expression); 497 } 498 499 public Predicate getFilterDirectory() { 500 return filterDirectory; 501 } 502 503 /** 504 * Filters the directory based on Simple language. 505 * For example to filter on current date, you can use a simple date pattern such as ${date:now:yyyMMdd} 506 */ 507 public void setFilterDirectory(Predicate filterDirectory) { 508 this.filterDirectory = filterDirectory; 509 } 510 511 /** 512 * Filters the directory based on Simple language. 513 * For example to filter on current date, you can use a simple date pattern such as ${date:now:yyyMMdd} 514 * @see #setFilterDirectory(Predicate) 515 */ 516 public void setFilterDirectory(String expression) { 517 this.filterDirectory = createFileLanguagePredicate(expression); 518 } 519 520 public Predicate getFilterFile() { 521 return filterFile; 522 } 523 524 /** 525 * Filters the file based on Simple language. 526 * For example to filter on file size, you can use ${file:size} > 5000 527 */ 528 public void setFilterFile(Predicate filterFile) { 529 this.filterFile = filterFile; 530 } 531 532 /** 533 * Filters the file based on Simple language. 534 * For example to filter on file size, you can use ${file:size} > 5000 535 * @see #setFilterFile(Predicate) 536 */ 537 public void setFilterFile(String expression) { 538 this.filterFile = createFileLanguagePredicate(expression); 539 } 540 541 public Expression getPreMove() { 542 return preMove; 543 } 544 545 /** 546 * Expression (such as File Language) used to dynamically set the filename when moving it before processing. 547 * For example to move in-progress files into the order directory set this value to order. 548 */ 549 public void setPreMove(Expression preMove) { 550 this.preMove = preMove; 551 } 552 553 public void setPreMove(String fileLanguageExpression) { 554 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 555 this.preMove = createFileLanguageExpression(expression); 556 } 557 558 public Expression getMoveExisting() { 559 return moveExisting; 560 } 561 562 /** 563 * Expression (such as File Language) used to compute file name to use when fileExist=Move is configured. 564 * To move files into a backup subdirectory just enter backup. 565 * This option only supports the following File Language tokens: "file:name", "file:name.ext", "file:name.noext", "file:onlyname", 566 * "file:onlyname.noext", "file:ext", and "file:parent". Notice the "file:parent" is not supported by the FTP component, 567 * as the FTP component can only move any existing files to a relative directory based on current dir as base. 568 */ 569 public void setMoveExisting(Expression moveExisting) { 570 this.moveExisting = moveExisting; 571 } 572 573 public void setMoveExisting(String fileLanguageExpression) { 574 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 575 this.moveExisting = createFileLanguageExpression(expression); 576 } 577 578 public Expression getFileName() { 579 return fileName; 580 } 581 582 /** 583 * Use Expression such as File Language to dynamically set the filename. 584 * For consumers, it's used as a filename filter. 585 * For producers, it's used to evaluate the filename to write. 586 * If an expression is set, it take precedence over the CamelFileName header. (Note: The header itself can also be an Expression). 587 * The expression options support both String and Expression types. 588 * If the expression is a String type, it is always evaluated using the File Language. 589 * If the expression is an Expression type, the specified Expression type is used - this allows you, 590 * for instance, to use OGNL expressions. For the consumer, you can use it to filter filenames, 591 * so you can for instance consume today's file using the File Language syntax: mydata-${date:now:yyyyMMdd}.txt. 592 * The producers support the CamelOverruleFileName header which takes precedence over any existing CamelFileName header; 593 * the CamelOverruleFileName is a header that is used only once, and makes it easier as this avoids to temporary 594 * store CamelFileName and have to restore it afterwards. 595 */ 596 public void setFileName(Expression fileName) { 597 this.fileName = fileName; 598 } 599 600 public void setFileName(String fileLanguageExpression) { 601 this.fileName = createFileLanguageExpression(fileLanguageExpression); 602 } 603 604 public String getDoneFileName() { 605 return doneFileName; 606 } 607 608 /** 609 * Producer: If provided, then Camel will write a 2nd done file when the original file has been written. 610 * The done file will be empty. This option configures what file name to use. 611 * Either you can specify a fixed name. Or you can use dynamic placeholders. 612 * The done file will always be written in the same folder as the original file. 613 * <p/> 614 * Consumer: If provided, Camel will only consume files if a done file exists. 615 * This option configures what file name to use. Either you can specify a fixed name. 616 * Or you can use dynamic placeholders.The done file is always expected in the same folder 617 * as the original file. 618 * <p/> 619 * Only ${file.name} and ${file.name.noext} is supported as dynamic placeholders. 620 */ 621 public void setDoneFileName(String doneFileName) { 622 this.doneFileName = doneFileName; 623 } 624 625 public Boolean isIdempotent() { 626 return idempotent != null ? idempotent : false; 627 } 628 629 public String getCharset() { 630 return charset; 631 } 632 633 /** 634 * This option is used to specify the encoding of the file. 635 * You can use this on the consumer, to specify the encodings of the files, which allow Camel to know the charset 636 * it should load the file content in case the file content is being accessed. 637 * Likewise when writing a file, you can use this option to specify which charset to write the file as well. 638 * Do mind that when writing the file Camel may have to read the message content into memory to be able to 639 * convert the data into the configured charset, so do not use this if you have big messages. 640 */ 641 public void setCharset(String charset) { 642 IOHelper.validateCharset(charset); 643 this.charset = charset; 644 } 645 646 protected boolean isIdempotentSet() { 647 return idempotent != null; 648 } 649 650 /** 651 * Option to use the Idempotent Consumer EIP pattern to let Camel skip already processed files. 652 * Will by default use a memory based LRUCache that holds 1000 entries. If noop=true then idempotent will be enabled 653 * as well to avoid consuming the same files over and over again. 654 */ 655 public void setIdempotent(Boolean idempotent) { 656 this.idempotent = idempotent; 657 } 658 659 public Expression getIdempotentKey() { 660 return idempotentKey; 661 } 662 663 /** 664 * To use a custom idempotent key. By default the absolute path of the file is used. 665 * You can use the File Language, for example to use the file name and file size, you can do: 666 * <tt>idempotentKey=${file:name}-${file:size}</tt> 667 */ 668 public void setIdempotentKey(Expression idempotentKey) { 669 this.idempotentKey = idempotentKey; 670 } 671 672 public void setIdempotentKey(String expression) { 673 this.idempotentKey = createFileLanguageExpression(expression); 674 } 675 676 public IdempotentRepository<String> getIdempotentRepository() { 677 return idempotentRepository; 678 } 679 680 /** 681 * A pluggable repository org.apache.camel.spi.IdempotentRepository which by default use MemoryMessageIdRepository 682 * if none is specified and idempotent is true. 683 */ 684 public void setIdempotentRepository(IdempotentRepository<String> idempotentRepository) { 685 this.idempotentRepository = idempotentRepository; 686 } 687 688 public GenericFileFilter<T> getFilter() { 689 return filter; 690 } 691 692 /** 693 * Pluggable filter as a org.apache.camel.component.file.GenericFileFilter class. 694 * Will skip files if filter returns false in its accept() method. 695 */ 696 public void setFilter(GenericFileFilter<T> filter) { 697 this.filter = filter; 698 } 699 700 public Comparator<GenericFile<T>> getSorter() { 701 return sorter; 702 } 703 704 /** 705 * Pluggable sorter as a java.util.Comparator<org.apache.camel.component.file.GenericFile> class. 706 */ 707 public void setSorter(Comparator<GenericFile<T>> sorter) { 708 this.sorter = sorter; 709 } 710 711 public Comparator<Exchange> getSortBy() { 712 return sortBy; 713 } 714 715 /** 716 * Built-in sort by using the File Language. 717 * Supports nested sorts, so you can have a sort by file name and as a 2nd group sort by modified date. 718 */ 719 public void setSortBy(Comparator<Exchange> sortBy) { 720 this.sortBy = sortBy; 721 } 722 723 public void setSortBy(String expression) { 724 setSortBy(expression, false); 725 } 726 727 public void setSortBy(String expression, boolean reverse) { 728 setSortBy(GenericFileDefaultSorter.sortByFileLanguage(getCamelContext(), expression, reverse)); 729 } 730 731 public boolean isShuffle() { 732 return shuffle; 733 } 734 735 /** 736 * To shuffle the list of files (sort in random order) 737 */ 738 public void setShuffle(boolean shuffle) { 739 this.shuffle = shuffle; 740 } 741 742 public String getTempPrefix() { 743 return tempPrefix; 744 } 745 746 /** 747 * This option is used to write the file using a temporary name and then, after the write is complete, 748 * rename it to the real name. Can be used to identify files being written and also avoid consumers 749 * (not using exclusive read locks) reading in progress files. Is often used by FTP when uploading big files. 750 */ 751 public void setTempPrefix(String tempPrefix) { 752 this.tempPrefix = tempPrefix; 753 // use only name as we set a prefix in from on the name 754 setTempFileName(tempPrefix + "${file:onlyname}"); 755 } 756 757 public Expression getTempFileName() { 758 return tempFileName; 759 } 760 761 /** 762 * The same as tempPrefix option but offering a more fine grained control on the naming of the temporary filename as it uses the File Language. 763 */ 764 public void setTempFileName(Expression tempFileName) { 765 this.tempFileName = tempFileName; 766 } 767 768 public void setTempFileName(String tempFileNameExpression) { 769 this.tempFileName = createFileLanguageExpression(tempFileNameExpression); 770 } 771 772 public boolean isEagerDeleteTargetFile() { 773 return eagerDeleteTargetFile; 774 } 775 776 /** 777 * Whether or not to eagerly delete any existing target file. 778 * This option only applies when you use fileExists=Override and the tempFileName option as well. 779 * You can use this to disable (set it to false) deleting the target file before the temp file is written. 780 * For example you may write big files and want the target file to exists during the temp file is being written. 781 * This ensure the target file is only deleted until the very last moment, just before the temp file is being 782 * renamed to the target filename. This option is also used to control whether to delete any existing files when 783 * fileExist=Move is enabled, and an existing file exists. 784 * If this option copyAndDeleteOnRenameFails false, then an exception will be thrown if an existing file existed, 785 * if its true, then the existing file is deleted before the move operation. 786 */ 787 public void setEagerDeleteTargetFile(boolean eagerDeleteTargetFile) { 788 this.eagerDeleteTargetFile = eagerDeleteTargetFile; 789 } 790 791 public GenericFileConfiguration getConfiguration() { 792 if (configuration == null) { 793 configuration = new GenericFileConfiguration(); 794 } 795 return configuration; 796 } 797 798 public void setConfiguration(GenericFileConfiguration configuration) { 799 this.configuration = configuration; 800 } 801 802 public GenericFileExclusiveReadLockStrategy<T> getExclusiveReadLockStrategy() { 803 return exclusiveReadLockStrategy; 804 } 805 806 /** 807 * Pluggable read-lock as a org.apache.camel.component.file.GenericFileExclusiveReadLockStrategy implementation. 808 */ 809 public void setExclusiveReadLockStrategy(GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy) { 810 this.exclusiveReadLockStrategy = exclusiveReadLockStrategy; 811 } 812 813 public String getReadLock() { 814 return readLock; 815 } 816 817 /** 818 * Used by consumer, to only poll the files if it has exclusive read-lock on the file (i.e. the file is not in-progress or being written). 819 * Camel will wait until the file lock is granted. 820 * <p/> 821 * This option provides the build in strategies: 822 * <ul> 823 * <li>none - No read lock is in use 824 * <li>markerFile - Camel creates a marker file (fileName.camelLock) and then holds a lock on it. This option is not available for the FTP component 825 * <li>changed - Changed is using file length/modification timestamp to detect whether the file is currently being copied or not. Will at least use 1 sec 826 * to determine this, so this option cannot consume files as fast as the others, but can be more reliable as the JDK IO API cannot 827 * always determine whether a file is currently being used by another process. The option readLockCheckInterval can be used to set the check frequency.</li> 828 * <li>fileLock - is for using java.nio.channels.FileLock. This option is not avail for the FTP component. This approach should be avoided when accessing 829 * a remote file system via a mount/share unless that file system supports distributed file locks.</li> 830 * <li>rename - rename is for using a try to rename the file as a test if we can get exclusive read-lock.</li> 831 * <li>idempotent - (only for file component) idempotent is for using a idempotentRepository as the read-lock. 832 * This allows to use read locks that supports clustering if the idempotent repository implementation supports that.</li> 833 * <li>idempotent-changed - (only for file component) idempotent-changed is for using a idempotentRepository and changed as the combined read-lock. 834 * This allows to use read locks that supports clustering if the idempotent repository implementation supports that.</li> 835 * <li>idempotent-rename - (only for file component) idempotent-rename is for using a idempotentRepository and rename as the combined read-lock. 836 * This allows to use read locks that supports clustering if the idempotent repository implementation supports that.</li> 837 * </ul> 838 * Notice: The various read locks is not all suited to work in clustered mode, where concurrent consumers on different nodes is competing 839 * for the same files on a shared file system. The markerFile using a close to atomic operation to create the empty marker file, 840 * but its not guaranteed to work in a cluster. The fileLock may work better but then the file system need to support distributed file locks, and so on. 841 * Using the idempotent read lock can support clustering if the idempotent repository supports clustering, such as Hazelcast Component or Infinispan. 842 */ 843 public void setReadLock(String readLock) { 844 this.readLock = readLock; 845 } 846 847 public long getReadLockCheckInterval() { 848 return readLockCheckInterval; 849 } 850 851 /** 852 * Interval in millis for the read-lock, if supported by the read lock. 853 * This interval is used for sleeping between attempts to acquire the read lock. 854 * For example when using the changed read lock, you can set a higher interval period to cater for slow writes. 855 * The default of 1 sec. may be too fast if the producer is very slow writing the file. 856 * <p/> 857 * Notice: For FTP the default readLockCheckInterval is 5000. 858 * <p/> 859 * The readLockTimeout value must be higher than readLockCheckInterval, but a rule of thumb is to have a timeout 860 * that is at least 2 or more times higher than the readLockCheckInterval. This is needed to ensure that amble 861 * time is allowed for the read lock process to try to grab the lock before the timeout was hit. 862 */ 863 public void setReadLockCheckInterval(long readLockCheckInterval) { 864 this.readLockCheckInterval = readLockCheckInterval; 865 } 866 867 public long getReadLockTimeout() { 868 return readLockTimeout; 869 } 870 871 /** 872 * Optional timeout in millis for the read-lock, if supported by the read-lock. 873 * If the read-lock could not be granted and the timeout triggered, then Camel will skip the file. 874 * At next poll Camel, will try the file again, and this time maybe the read-lock could be granted. 875 * Use a value of 0 or lower to indicate forever. Currently fileLock, changed and rename support the timeout. 876 * <p/> 877 * Notice: For FTP the default readLockTimeout value is 20000 instead of 10000. 878 * <p/> 879 * The readLockTimeout value must be higher than readLockCheckInterval, but a rule of thumb is to have a timeout 880 * that is at least 2 or more times higher than the readLockCheckInterval. This is needed to ensure that amble 881 * time is allowed for the read lock process to try to grab the lock before the timeout was hit. 882 */ 883 public void setReadLockTimeout(long readLockTimeout) { 884 this.readLockTimeout = readLockTimeout; 885 } 886 887 public boolean isReadLockMarkerFile() { 888 return readLockMarkerFile; 889 } 890 891 /** 892 * Whether to use marker file with the changed, rename, or exclusive read lock types. 893 * By default a marker file is used as well to guard against other processes picking up the same files. 894 * This behavior can be turned off by setting this option to false. 895 * For example if you do not want to write marker files to the file systems by the Camel application. 896 */ 897 public void setReadLockMarkerFile(boolean readLockMarkerFile) { 898 this.readLockMarkerFile = readLockMarkerFile; 899 } 900 901 public boolean isReadLockDeleteOrphanLockFiles() { 902 return readLockDeleteOrphanLockFiles; 903 } 904 905 /** 906 * Whether or not read lock with marker files should upon startup delete any orphan read lock files, which may 907 * have been left on the file system, if Camel was not properly shutdown (such as a JVM crash). 908 * <p/> 909 * If turning this option to <tt>false</tt> then any orphaned lock file will cause Camel to not attempt to pickup 910 * that file, this could also be due another node is concurrently reading files from the same shared directory. 911 */ 912 public void setReadLockDeleteOrphanLockFiles(boolean readLockDeleteOrphanLockFiles) { 913 this.readLockDeleteOrphanLockFiles = readLockDeleteOrphanLockFiles; 914 } 915 916 public LoggingLevel getReadLockLoggingLevel() { 917 return readLockLoggingLevel; 918 } 919 920 /** 921 * Logging level used when a read lock could not be acquired. 922 * By default a WARN is logged. 923 * You can change this level, for example to OFF to not have any logging. 924 * This option is only applicable for readLock of types: changed, fileLock, idempotent, idempotent-changed, idempotent-rename, rename. 925 */ 926 public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) { 927 this.readLockLoggingLevel = readLockLoggingLevel; 928 } 929 930 public long getReadLockMinLength() { 931 return readLockMinLength; 932 } 933 934 /** 935 * This option applied only for readLock=changed. This option allows you to configure a minimum file length. 936 * By default Camel expects the file to contain data, and thus the default value is 1. 937 * You can set this option to zero, to allow consuming zero-length files. 938 */ 939 public void setReadLockMinLength(long readLockMinLength) { 940 this.readLockMinLength = readLockMinLength; 941 } 942 943 public long getReadLockMinAge() { 944 return readLockMinAge; 945 } 946 947 /** 948 * This option applied only for readLock=change. 949 * This option allows to specify a minimum age the file must be before attempting to acquire the read lock. 950 * For example use readLockMinAge=300s to require the file is at last 5 minutes old. 951 * This can speedup the changed read lock as it will only attempt to acquire files which are at least that given age. 952 */ 953 public void setReadLockMinAge(long readLockMinAge) { 954 this.readLockMinAge = readLockMinAge; 955 } 956 957 public boolean isReadLockRemoveOnRollback() { 958 return readLockRemoveOnRollback; 959 } 960 961 /** 962 * This option applied only for readLock=idempotent. 963 * This option allows to specify whether to remove the file name entry from the idempotent repository 964 * when processing the file failed and a rollback happens. 965 * If this option is false, then the file name entry is confirmed (as if the file did a commit). 966 */ 967 public void setReadLockRemoveOnRollback(boolean readLockRemoveOnRollback) { 968 this.readLockRemoveOnRollback = readLockRemoveOnRollback; 969 } 970 971 public boolean isReadLockRemoveOnCommit() { 972 return readLockRemoveOnCommit; 973 } 974 975 /** 976 * This option applied only for readLock=idempotent. 977 * This option allows to specify whether to remove the file name entry from the idempotent repository 978 * when processing the file is succeeded and a commit happens. 979 * <p/> 980 * By default the file is not removed which ensures that any race-condition do not occur so another active 981 * node may attempt to grab the file. Instead the idempotent repository may support eviction strategies 982 * that you can configure to evict the file name entry after X minutes - this ensures no problems with race conditions. 983 */ 984 public void setReadLockRemoveOnCommit(boolean readLockRemoveOnCommit) { 985 this.readLockRemoveOnCommit = readLockRemoveOnCommit; 986 } 987 988 public int getBufferSize() { 989 return bufferSize; 990 } 991 992 /** 993 * Write buffer sized in bytes. 994 */ 995 public void setBufferSize(int bufferSize) { 996 if (bufferSize <= 0) { 997 throw new IllegalArgumentException("BufferSize must be a positive value, was " + bufferSize); 998 } 999 this.bufferSize = bufferSize; 1000 } 1001 1002 public GenericFileExist getFileExist() { 1003 return fileExist; 1004 } 1005 1006 /** 1007 * What to do if a file already exists with the same name. 1008 * 1009 * Override, which is the default, replaces the existing file. 1010 * <ul> 1011 * <li>Append - adds content to the existing file.</li> 1012 * <li>Fail - throws a GenericFileOperationException, indicating that there is already an existing file.</li> 1013 * <li>Ignore - silently ignores the problem and does not override the existing file, but assumes everything is okay.</li> 1014 * <li>Move - option requires to use the moveExisting option to be configured as well. 1015 * The option eagerDeleteTargetFile can be used to control what to do if an moving the file, and there exists already an existing file, 1016 * otherwise causing the move operation to fail. 1017 * The Move option will move any existing files, before writing the target file.</li> 1018 * <li>TryRename is only applicable if tempFileName option is in use. This allows to try renaming the file from the temporary name to the actual name, 1019 * without doing any exists check. This check may be faster on some file systems and especially FTP servers.</li> 1020 * </ul> 1021 */ 1022 public void setFileExist(GenericFileExist fileExist) { 1023 this.fileExist = fileExist; 1024 } 1025 1026 public boolean isAutoCreate() { 1027 return autoCreate; 1028 } 1029 1030 /** 1031 * Automatically create missing directories in the file's pathname. For the file consumer, that means creating the starting directory. 1032 * For the file producer, it means the directory the files should be written to. 1033 */ 1034 public void setAutoCreate(boolean autoCreate) { 1035 this.autoCreate = autoCreate; 1036 } 1037 1038 public boolean isStartingDirectoryMustExist() { 1039 return startingDirectoryMustExist; 1040 } 1041 1042 /** 1043 * Whether the starting directory must exist. Mind that the autoCreate option is default enabled, 1044 * which means the starting directory is normally auto created if it doesn't exist. 1045 * You can disable autoCreate and enable this to ensure the starting directory must exist. Will thrown an exception if the directory doesn't exist. 1046 */ 1047 public void setStartingDirectoryMustExist(boolean startingDirectoryMustExist) { 1048 this.startingDirectoryMustExist = startingDirectoryMustExist; 1049 } 1050 1051 public boolean isDirectoryMustExist() { 1052 return directoryMustExist; 1053 } 1054 1055 /** 1056 * Similar to startingDirectoryMustExist but this applies during polling recursive sub directories. 1057 */ 1058 public void setDirectoryMustExist(boolean directoryMustExist) { 1059 this.directoryMustExist = directoryMustExist; 1060 } 1061 1062 public GenericFileProcessStrategy<T> getProcessStrategy() { 1063 return processStrategy; 1064 } 1065 1066 /** 1067 * A pluggable org.apache.camel.component.file.GenericFileProcessStrategy allowing you to implement your own readLock option or similar. 1068 * Can also be used when special conditions must be met before a file can be consumed, such as a special ready file exists. 1069 * If this option is set then the readLock option does not apply. 1070 */ 1071 public void setProcessStrategy(GenericFileProcessStrategy<T> processStrategy) { 1072 this.processStrategy = processStrategy; 1073 } 1074 1075 public String getLocalWorkDirectory() { 1076 return localWorkDirectory; 1077 } 1078 1079 /** 1080 * When consuming, a local work directory can be used to store the remote file content directly in local files, 1081 * to avoid loading the content into memory. This is beneficial, if you consume a very big remote file and thus can conserve memory. 1082 */ 1083 public void setLocalWorkDirectory(String localWorkDirectory) { 1084 this.localWorkDirectory = localWorkDirectory; 1085 } 1086 1087 public int getMaxMessagesPerPoll() { 1088 return maxMessagesPerPoll; 1089 } 1090 1091 /** 1092 * To define a maximum messages to gather per poll. 1093 * By default no maximum is set. Can be used to set a limit of e.g. 1000 to avoid when starting up the server that there are thousands of files. 1094 * Set a value of 0 or negative to disabled it. 1095 * Notice: If this option is in use then the File and FTP components will limit before any sorting. 1096 * For example if you have 100000 files and use maxMessagesPerPoll=500, then only the first 500 files will be picked up, and then sorted. 1097 * You can use the eagerMaxMessagesPerPoll option and set this to false to allow to scan all files first and then sort afterwards. 1098 */ 1099 public void setMaxMessagesPerPoll(int maxMessagesPerPoll) { 1100 this.maxMessagesPerPoll = maxMessagesPerPoll; 1101 } 1102 1103 public boolean isEagerMaxMessagesPerPoll() { 1104 return eagerMaxMessagesPerPoll; 1105 } 1106 1107 /** 1108 * Allows for controlling whether the limit from maxMessagesPerPoll is eager or not. 1109 * If eager then the limit is during the scanning of files. Where as false would scan all files, and then perform sorting. 1110 * Setting this option to false allows for sorting all files first, and then limit the poll. Mind that this requires a 1111 * higher memory usage as all file details are in memory to perform the sorting. 1112 */ 1113 public void setEagerMaxMessagesPerPoll(boolean eagerMaxMessagesPerPoll) { 1114 this.eagerMaxMessagesPerPoll = eagerMaxMessagesPerPoll; 1115 } 1116 1117 public int getMaxDepth() { 1118 return maxDepth; 1119 } 1120 1121 /** 1122 * The maximum depth to traverse when recursively processing a directory. 1123 */ 1124 public void setMaxDepth(int maxDepth) { 1125 this.maxDepth = maxDepth; 1126 } 1127 1128 public int getMinDepth() { 1129 return minDepth; 1130 } 1131 1132 /** 1133 * The minimum depth to start processing when recursively processing a directory. 1134 * Using minDepth=1 means the base directory. Using minDepth=2 means the first sub directory. 1135 */ 1136 public void setMinDepth(int minDepth) { 1137 this.minDepth = minDepth; 1138 } 1139 1140 public IdempotentRepository<String> getInProgressRepository() { 1141 return inProgressRepository; 1142 } 1143 1144 /** 1145 * A pluggable in-progress repository org.apache.camel.spi.IdempotentRepository. 1146 * The in-progress repository is used to account the current in progress files being consumed. By default a memory based repository is used. 1147 */ 1148 public void setInProgressRepository(IdempotentRepository<String> inProgressRepository) { 1149 this.inProgressRepository = inProgressRepository; 1150 } 1151 1152 public boolean isKeepLastModified() { 1153 return keepLastModified; 1154 } 1155 1156 /** 1157 * Will keep the last modified timestamp from the source file (if any). 1158 * Will use the Exchange.FILE_LAST_MODIFIED header to located the timestamp. 1159 * This header can contain either a java.util.Date or long with the timestamp. 1160 * If the timestamp exists and the option is enabled it will set this timestamp on the written file. 1161 * Note: This option only applies to the file producer. You cannot use this option with any of the ftp producers. 1162 */ 1163 public void setKeepLastModified(boolean keepLastModified) { 1164 this.keepLastModified = keepLastModified; 1165 } 1166 1167 public boolean isAllowNullBody() { 1168 return allowNullBody; 1169 } 1170 1171 /** 1172 * Used to specify if a null body is allowed during file writing. 1173 * If set to true then an empty file will be created, when set to false, and attempting to send a null body to the file component, 1174 * a GenericFileWriteException of 'Cannot write null body to file.' will be thrown. 1175 * If the `fileExist` option is set to 'Override', then the file will be truncated, and if set to `append` the file will remain unchanged. 1176 */ 1177 public void setAllowNullBody(boolean allowNullBody) { 1178 this.allowNullBody = allowNullBody; 1179 } 1180 1181 public ExceptionHandler getOnCompletionExceptionHandler() { 1182 return onCompletionExceptionHandler; 1183 } 1184 1185 /** 1186 * To use a custom {@link org.apache.camel.spi.ExceptionHandler} to handle any thrown exceptions that happens 1187 * during the file on completion process where the consumer does either a commit or rollback. The default 1188 * implementation will log any exception at WARN level and ignore. 1189 */ 1190 public void setOnCompletionExceptionHandler(ExceptionHandler onCompletionExceptionHandler) { 1191 this.onCompletionExceptionHandler = onCompletionExceptionHandler; 1192 } 1193 1194 /** 1195 * Configures the given message with the file which sets the body to the 1196 * file object. 1197 */ 1198 public void configureMessage(GenericFile<T> file, Message message) { 1199 message.setBody(file); 1200 1201 if (flatten) { 1202 // when flatten the file name should not contain any paths 1203 message.setHeader(Exchange.FILE_NAME, file.getFileNameOnly()); 1204 } else { 1205 // compute name to set on header that should be relative to starting directory 1206 String name = file.isAbsolute() ? file.getAbsoluteFilePath() : file.getRelativeFilePath(); 1207 1208 // skip leading endpoint configured directory 1209 String endpointPath = getConfiguration().getDirectory() + getFileSeparator(); 1210 1211 // need to normalize paths to ensure we can match using startsWith 1212 endpointPath = FileUtil.normalizePath(endpointPath); 1213 String copyOfName = FileUtil.normalizePath(name); 1214 if (ObjectHelper.isNotEmpty(endpointPath) && copyOfName.startsWith(endpointPath)) { 1215 name = name.substring(endpointPath.length()); 1216 } 1217 1218 // adjust filename 1219 message.setHeader(Exchange.FILE_NAME, name); 1220 } 1221 } 1222 1223 /** 1224 * Set up the exchange properties with the options of the file endpoint 1225 */ 1226 public void configureExchange(Exchange exchange) { 1227 // Now we just set the charset property here 1228 if (getCharset() != null) { 1229 exchange.setProperty(Exchange.CHARSET_NAME, getCharset()); 1230 } 1231 } 1232 1233 /** 1234 * Strategy to configure the move, preMove, or moveExisting option based on a String input. 1235 * 1236 * @param expression the original string input 1237 * @return configured string or the original if no modifications is needed 1238 */ 1239 protected String configureMoveOrPreMoveExpression(String expression) { 1240 // if the expression already have ${ } placeholders then pass it unmodified 1241 if (StringHelper.hasStartToken(expression, "simple")) { 1242 return expression; 1243 } 1244 1245 // remove trailing slash 1246 expression = FileUtil.stripTrailingSeparator(expression); 1247 1248 StringBuilder sb = new StringBuilder(); 1249 1250 // if relative then insert start with the parent folder 1251 if (!isAbsolute(expression)) { 1252 sb.append("${file:parent}"); 1253 sb.append(getFileSeparator()); 1254 } 1255 // insert the directory the end user provided 1256 sb.append(expression); 1257 // append only the filename (file:name can contain a relative path, so we must use onlyname) 1258 sb.append(getFileSeparator()); 1259 sb.append("${file:onlyname}"); 1260 1261 return sb.toString(); 1262 } 1263 1264 protected Map<String, Object> getParamsAsMap() { 1265 Map<String, Object> params = new HashMap<String, Object>(); 1266 1267 if (isNoop()) { 1268 params.put("noop", Boolean.toString(true)); 1269 } 1270 if (isDelete()) { 1271 params.put("delete", Boolean.toString(true)); 1272 } 1273 if (move != null) { 1274 params.put("move", move); 1275 } 1276 if (moveFailed != null) { 1277 params.put("moveFailed", moveFailed); 1278 } 1279 if (preMove != null) { 1280 params.put("preMove", preMove); 1281 } 1282 if (exclusiveReadLockStrategy != null) { 1283 params.put("exclusiveReadLockStrategy", exclusiveReadLockStrategy); 1284 } 1285 if (readLock != null) { 1286 params.put("readLock", readLock); 1287 } 1288 if ("idempotent".equals(readLock) || "idempotent-changed".equals(readLock) || "idempotent-rename".equals(readLock)) { 1289 params.put("readLockIdempotentRepository", idempotentRepository); 1290 } 1291 if (readLockCheckInterval > 0) { 1292 params.put("readLockCheckInterval", readLockCheckInterval); 1293 } 1294 if (readLockTimeout > 0) { 1295 params.put("readLockTimeout", readLockTimeout); 1296 } 1297 params.put("readLockMarkerFile", readLockMarkerFile); 1298 params.put("readLockDeleteOrphanLockFiles", readLockDeleteOrphanLockFiles); 1299 params.put("readLockMinLength", readLockMinLength); 1300 params.put("readLockLoggingLevel", readLockLoggingLevel); 1301 params.put("readLockMinAge", readLockMinAge); 1302 params.put("readLockRemoveOnRollback", readLockRemoveOnRollback); 1303 params.put("readLockRemoveOnCommit", readLockRemoveOnCommit); 1304 return params; 1305 } 1306 1307 private Expression createFileLanguageExpression(String expression) { 1308 Language language; 1309 // only use file language if the name is complex (eg. using $) 1310 if (expression.contains("$")) { 1311 language = getCamelContext().resolveLanguage("file"); 1312 } else { 1313 language = getCamelContext().resolveLanguage("constant"); 1314 } 1315 return language.createExpression(expression); 1316 } 1317 1318 private Predicate createFileLanguagePredicate(String expression) { 1319 Language language = getCamelContext().resolveLanguage("file"); 1320 return language.createPredicate(expression); 1321 } 1322 1323 /** 1324 * Creates the associated name of the done file based on the given file name. 1325 * <p/> 1326 * This method should only be invoked if a done filename property has been set on this endpoint. 1327 * 1328 * @param fileName the file name 1329 * @return name of the associated done file name 1330 */ 1331 protected String createDoneFileName(String fileName) { 1332 String pattern = getDoneFileName(); 1333 ObjectHelper.notEmpty(pattern, "doneFileName", pattern); 1334 1335 // we only support ${file:name} or ${file:name.noext} as dynamic placeholders for done files 1336 String path = FileUtil.onlyPath(fileName); 1337 String onlyName = Matcher.quoteReplacement(FileUtil.stripPath(fileName)); 1338 1339 pattern = pattern.replaceFirst("\\$\\{file:name\\}", onlyName); 1340 pattern = pattern.replaceFirst("\\$simple\\{file:name\\}", onlyName); 1341 pattern = pattern.replaceFirst("\\$\\{file:name.noext\\}", FileUtil.stripExt(onlyName)); 1342 pattern = pattern.replaceFirst("\\$simple\\{file:name.noext\\}", FileUtil.stripExt(onlyName)); 1343 1344 // must be able to resolve all placeholders supported 1345 if (StringHelper.hasStartToken(pattern, "simple")) { 1346 throw new ExpressionIllegalSyntaxException(fileName + ". Cannot resolve reminder: " + pattern); 1347 } 1348 1349 String answer = pattern; 1350 if (ObjectHelper.isNotEmpty(path) && ObjectHelper.isNotEmpty(pattern)) { 1351 // done file must always be in same directory as the real file name 1352 answer = path + getFileSeparator() + pattern; 1353 } 1354 1355 if (getConfiguration().needToNormalize()) { 1356 // must normalize path to cater for Windows and other OS 1357 answer = FileUtil.normalizePath(answer); 1358 } 1359 1360 return answer; 1361 } 1362 1363 /** 1364 * Is the given file a done file? 1365 * <p/> 1366 * This method should only be invoked if a done filename property has been set on this endpoint. 1367 * 1368 * @param fileName the file name 1369 * @return <tt>true</tt> if its a done file, <tt>false</tt> otherwise 1370 */ 1371 protected boolean isDoneFile(String fileName) { 1372 String pattern = getDoneFileName(); 1373 ObjectHelper.notEmpty(pattern, "doneFileName", pattern); 1374 1375 if (!StringHelper.hasStartToken(pattern, "simple")) { 1376 // no tokens, so just match names directly 1377 return pattern.equals(fileName); 1378 } 1379 1380 // the static part of the pattern, is that a prefix or suffix? 1381 // its a prefix if ${ start token is not at the start of the pattern 1382 boolean prefix = pattern.indexOf("${") > 0; 1383 1384 // remove dynamic parts of the pattern so we only got the static part left 1385 pattern = pattern.replaceFirst("\\$\\{file:name\\}", ""); 1386 pattern = pattern.replaceFirst("\\$simple\\{file:name\\}", ""); 1387 pattern = pattern.replaceFirst("\\$\\{file:name.noext\\}", ""); 1388 pattern = pattern.replaceFirst("\\$simple\\{file:name.noext\\}", ""); 1389 1390 // must be able to resolve all placeholders supported 1391 if (StringHelper.hasStartToken(pattern, "simple")) { 1392 throw new ExpressionIllegalSyntaxException(fileName + ". Cannot resolve reminder: " + pattern); 1393 } 1394 1395 if (prefix) { 1396 return fileName.startsWith(pattern); 1397 } else { 1398 return fileName.endsWith(pattern); 1399 } 1400 } 1401 1402 @Override 1403 protected void doStart() throws Exception { 1404 // validate that the read lock options is valid for the process strategy 1405 if (!"none".equals(readLock) && !"off".equals(readLock)) { 1406 if (readLockTimeout > 0 && readLockTimeout <= readLockCheckInterval) { 1407 throw new IllegalArgumentException("The option readLockTimeout must be higher than readLockCheckInterval" 1408 + ", was readLockTimeout=" + readLockTimeout + ", readLockCheckInterval=" + readLockCheckInterval 1409 + ". A good practice is to let the readLockTimeout be at least 3 times higher than the readLockCheckInterval" 1410 + " to ensure that the read lock procedure has enough time to acquire the lock."); 1411 } 1412 } 1413 if ("idempotent".equals(readLock) && idempotentRepository == null) { 1414 throw new IllegalArgumentException("IdempotentRepository must be configured when using readLock=idempotent"); 1415 } 1416 1417 if (antInclude != null) { 1418 if (antFilter == null) { 1419 antFilter = new AntPathMatcherGenericFileFilter<>(); 1420 } 1421 antFilter.setIncludes(antInclude); 1422 } 1423 if (antExclude != null) { 1424 if (antFilter == null) { 1425 antFilter = new AntPathMatcherGenericFileFilter<>(); 1426 } 1427 antFilter.setExcludes(antExclude); 1428 } 1429 if (antFilter != null) { 1430 antFilter.setCaseSensitive(antFilterCaseSensitive); 1431 } 1432 1433 // idempotent repository may be used by others, so add it as a service so its stopped when CamelContext stops 1434 if (idempotentRepository != null) { 1435 getCamelContext().addService(idempotentRepository, true); 1436 } 1437 ServiceHelper.startServices(inProgressRepository); 1438 super.doStart(); 1439 } 1440 1441 @Override 1442 protected void doStop() throws Exception { 1443 super.doStop(); 1444 ServiceHelper.stopServices(inProgressRepository); 1445 } 1446}