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