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