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