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