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