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 */ 017 package org.apache.camel.component.file; 018 019 import java.io.IOException; 020 import java.lang.reflect.Method; 021 import java.util.ArrayList; 022 import java.util.Comparator; 023 import java.util.HashMap; 024 import java.util.List; 025 import java.util.Map; 026 027 import org.apache.camel.CamelContext; 028 import org.apache.camel.Component; 029 import org.apache.camel.Exchange; 030 import org.apache.camel.Expression; 031 import org.apache.camel.ExpressionIllegalSyntaxException; 032 import org.apache.camel.LoggingLevel; 033 import org.apache.camel.Message; 034 import org.apache.camel.Processor; 035 import org.apache.camel.impl.ScheduledPollEndpoint; 036 import org.apache.camel.processor.idempotent.MemoryIdempotentRepository; 037 import org.apache.camel.spi.BrowsableEndpoint; 038 import org.apache.camel.spi.FactoryFinder; 039 import org.apache.camel.spi.IdempotentRepository; 040 import org.apache.camel.spi.Language; 041 import org.apache.camel.spi.UriParam; 042 import org.apache.camel.util.FileUtil; 043 import org.apache.camel.util.IOHelper; 044 import org.apache.camel.util.ObjectHelper; 045 import org.apache.camel.util.ServiceHelper; 046 import org.apache.camel.util.StringHelper; 047 import org.slf4j.Logger; 048 import org.slf4j.LoggerFactory; 049 050 /** 051 * Base class for file endpoints 052 */ 053 public abstract class GenericFileEndpoint<T> extends ScheduledPollEndpoint implements BrowsableEndpoint { 054 055 protected static final String DEFAULT_STRATEGYFACTORY_CLASS = "org.apache.camel.component.file.strategy.GenericFileProcessStrategyFactory"; 056 protected static final int DEFAULT_IDEMPOTENT_CACHE_SIZE = 1000; 057 058 protected final Logger log = LoggerFactory.getLogger(getClass()); 059 060 protected GenericFileConfiguration configuration; 061 062 @UriParam 063 protected GenericFileProcessStrategy<T> processStrategy; 064 @UriParam 065 protected IdempotentRepository<String> inProgressRepository = new MemoryIdempotentRepository(); 066 @UriParam 067 protected String localWorkDirectory; 068 @UriParam 069 protected boolean autoCreate = true; 070 @UriParam 071 protected boolean startingDirectoryMustExist; 072 @UriParam 073 protected boolean directoryMustExist; 074 @UriParam 075 protected int bufferSize = FileUtil.BUFFER_SIZE; 076 @UriParam 077 protected GenericFileExist fileExist = GenericFileExist.Override; 078 @UriParam 079 protected boolean noop; 080 @UriParam 081 protected boolean recursive; 082 @UriParam 083 protected boolean delete; 084 @UriParam 085 protected boolean flatten; 086 @UriParam 087 protected int maxMessagesPerPoll; 088 @UriParam 089 protected boolean eagerMaxMessagesPerPoll = true; 090 @UriParam 091 protected int maxDepth = Integer.MAX_VALUE; 092 @UriParam 093 protected int minDepth; 094 @UriParam 095 protected String tempPrefix; 096 @UriParam 097 protected Expression tempFileName; 098 @UriParam 099 protected boolean eagerDeleteTargetFile = true; 100 @UriParam 101 protected String include; 102 @UriParam 103 protected String exclude; 104 @UriParam 105 protected String charset; 106 @UriParam 107 protected Expression fileName; 108 @UriParam 109 protected Expression move; 110 @UriParam 111 protected Expression moveFailed; 112 @UriParam 113 protected Expression preMove; 114 @UriParam 115 protected Expression moveExisting; 116 @UriParam 117 protected Boolean idempotent; 118 @UriParam 119 protected Expression idempotentKey; 120 @UriParam 121 protected IdempotentRepository<String> idempotentRepository; 122 @UriParam 123 protected GenericFileFilter<T> filter; 124 @UriParam 125 protected AntPathMatcherGenericFileFilter<T> antFilter; 126 @UriParam 127 protected Comparator<GenericFile<T>> sorter; 128 @UriParam 129 protected Comparator<Exchange> sortBy; 130 @UriParam 131 protected String readLock = "none"; 132 @UriParam 133 protected long readLockCheckInterval = 1000; 134 @UriParam 135 protected long readLockTimeout = 10000; 136 @UriParam 137 protected LoggingLevel readLockLoggingLevel = LoggingLevel.WARN; 138 @UriParam 139 protected long readLockMinLength = 1; 140 @UriParam 141 protected GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy; 142 @UriParam 143 protected boolean keepLastModified; 144 @UriParam 145 protected String doneFileName; 146 @UriParam 147 protected boolean allowNullBody; 148 149 public GenericFileEndpoint() { 150 } 151 152 public GenericFileEndpoint(String endpointUri, Component component) { 153 super(endpointUri, component); 154 } 155 156 public boolean isSingleton() { 157 return true; 158 } 159 160 public abstract GenericFileConsumer<T> createConsumer(Processor processor) throws Exception; 161 162 public abstract GenericFileProducer<T> createProducer() throws Exception; 163 164 public abstract Exchange createExchange(GenericFile<T> file); 165 166 public abstract String getScheme(); 167 168 public abstract char getFileSeparator(); 169 170 public abstract boolean isAbsolute(String name); 171 172 /** 173 * Return the file name that will be auto-generated for the given message if 174 * none is provided 175 */ 176 public String getGeneratedFileName(Message message) { 177 return StringHelper.sanitize(message.getMessageId()); 178 } 179 180 public GenericFileProcessStrategy<T> getGenericFileProcessStrategy() { 181 if (processStrategy == null) { 182 processStrategy = createGenericFileStrategy(); 183 log.debug("Using Generic file process strategy: {}", processStrategy); 184 } 185 return processStrategy; 186 } 187 188 /** 189 * This implementation will <b>not</b> load the file content. 190 * Any file locking is neither in use by this implementation.. 191 */ 192 @Override 193 public List<Exchange> getExchanges() { 194 final List<Exchange> answer = new ArrayList<Exchange>(); 195 196 GenericFileConsumer<?> consumer = null; 197 try { 198 // create a new consumer which can poll the exchanges we want to browse 199 // do not provide a processor as we do some custom processing 200 consumer = createConsumer(null); 201 consumer.setCustomProcessor(new Processor() { 202 @Override 203 public void process(Exchange exchange) throws Exception { 204 answer.add(exchange); 205 } 206 }); 207 // do not start scheduler, as we invoke the poll manually 208 consumer.setStartScheduler(false); 209 // start consumer 210 ServiceHelper.startService(consumer); 211 // invoke poll which performs the custom processing, so we can browse the exchanges 212 consumer.poll(); 213 } catch (Exception e) { 214 throw ObjectHelper.wrapRuntimeCamelException(e); 215 } finally { 216 try { 217 ServiceHelper.stopService(consumer); 218 } catch (Exception e) { 219 log.debug("Error stopping consumer used for browsing exchanges. This exception will be ignored", e); 220 } 221 } 222 223 return answer; 224 } 225 226 /** 227 * A strategy method to lazily create the file strategy 228 */ 229 @SuppressWarnings("unchecked") 230 protected GenericFileProcessStrategy<T> createGenericFileStrategy() { 231 Class<?> factory = null; 232 try { 233 FactoryFinder finder = getCamelContext().getFactoryFinder("META-INF/services/org/apache/camel/component/"); 234 log.trace("Using FactoryFinder: {}", finder); 235 factory = finder.findClass(getScheme(), "strategy.factory.", CamelContext.class); 236 } catch (ClassNotFoundException e) { 237 log.trace("'strategy.factory.class' not found", e); 238 } catch (IOException e) { 239 log.trace("No strategy factory defined in 'META-INF/services/org/apache/camel/component/'", e); 240 } 241 242 if (factory == null) { 243 // use default 244 try { 245 log.trace("Using ClassResolver to resolve class: {}", DEFAULT_STRATEGYFACTORY_CLASS); 246 factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS); 247 } catch (Exception e) { 248 log.trace("Cannot load class: {}", DEFAULT_STRATEGYFACTORY_CLASS, e); 249 } 250 // fallback and us this class loader 251 try { 252 if (log.isTraceEnabled()) { 253 log.trace("Using classloader: {} to resolve class: {}", this.getClass().getClassLoader(), DEFAULT_STRATEGYFACTORY_CLASS); 254 } 255 factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS, this.getClass().getClassLoader()); 256 } catch (Exception e) { 257 if (log.isTraceEnabled()) { 258 log.trace("Cannot load class: {} using classloader: " + this.getClass().getClassLoader(), DEFAULT_STRATEGYFACTORY_CLASS, e); 259 } 260 } 261 262 if (factory == null) { 263 throw new TypeNotPresentException(DEFAULT_STRATEGYFACTORY_CLASS + " class not found", null); 264 } 265 } 266 267 try { 268 Method factoryMethod = factory.getMethod("createGenericFileProcessStrategy", CamelContext.class, Map.class); 269 Map<String, Object> params = getParamsAsMap(); 270 log.debug("Parameters for Generic file process strategy {}", params); 271 return (GenericFileProcessStrategy<T>) ObjectHelper.invokeMethod(factoryMethod, null, getCamelContext(), params); 272 } catch (NoSuchMethodException e) { 273 throw new TypeNotPresentException(factory.getSimpleName() + ".createGenericFileProcessStrategy method not found", e); 274 } 275 } 276 277 public boolean isNoop() { 278 return noop; 279 } 280 281 public void setNoop(boolean noop) { 282 this.noop = noop; 283 } 284 285 public boolean isRecursive() { 286 return recursive; 287 } 288 289 public void setRecursive(boolean recursive) { 290 this.recursive = recursive; 291 } 292 293 public String getInclude() { 294 return include; 295 } 296 297 public void setInclude(String include) { 298 this.include = include; 299 } 300 301 public String getExclude() { 302 return exclude; 303 } 304 305 public void setExclude(String exclude) { 306 this.exclude = exclude; 307 } 308 309 public void setAntInclude(String antInclude) { 310 if (this.antFilter == null) { 311 this.antFilter = new AntPathMatcherGenericFileFilter<T>(); 312 } 313 this.antFilter.setIncludes(antInclude); 314 } 315 316 public void setAntExclude(String antExclude) { 317 if (this.antFilter == null) { 318 this.antFilter = new AntPathMatcherGenericFileFilter<T>(); 319 } 320 this.antFilter.setExcludes(antExclude); 321 } 322 323 /** 324 * Sets case sensitive flag on {@link org.apache.camel.component.file.AntPathMatcherFileFilter} 325 * <p/> 326 * Is by default turned on <tt>true</tt>. 327 */ 328 public void setAntFilterCaseSensitive(boolean antFilterCaseSensitive) { 329 if (this.antFilter == null) { 330 this.antFilter = new AntPathMatcherGenericFileFilter<T>(); 331 } 332 this.antFilter.setCaseSensitive(antFilterCaseSensitive); 333 } 334 335 public GenericFileFilter<T> getAntFilter() { 336 return antFilter; 337 } 338 339 public boolean isDelete() { 340 return delete; 341 } 342 343 public void setDelete(boolean delete) { 344 this.delete = delete; 345 } 346 347 public boolean isFlatten() { 348 return flatten; 349 } 350 351 public void setFlatten(boolean flatten) { 352 this.flatten = flatten; 353 } 354 355 public Expression getMove() { 356 return move; 357 } 358 359 public void setMove(Expression move) { 360 this.move = move; 361 } 362 363 /** 364 * Sets the move failure expression based on 365 * {@link org.apache.camel.language.simple.SimpleLanguage} 366 */ 367 public void setMoveFailed(String fileLanguageExpression) { 368 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 369 this.moveFailed = createFileLanguageExpression(expression); 370 } 371 372 public Expression getMoveFailed() { 373 return moveFailed; 374 } 375 376 public void setMoveFailed(Expression moveFailed) { 377 this.moveFailed = moveFailed; 378 } 379 380 /** 381 * Sets the move expression based on 382 * {@link org.apache.camel.language.simple.SimpleLanguage} 383 */ 384 public void setMove(String fileLanguageExpression) { 385 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 386 this.move = createFileLanguageExpression(expression); 387 } 388 389 public Expression getPreMove() { 390 return preMove; 391 } 392 393 public void setPreMove(Expression preMove) { 394 this.preMove = preMove; 395 } 396 397 /** 398 * Sets the pre move expression based on 399 * {@link org.apache.camel.language.simple.SimpleLanguage} 400 */ 401 public void setPreMove(String fileLanguageExpression) { 402 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 403 this.preMove = createFileLanguageExpression(expression); 404 } 405 406 public Expression getMoveExisting() { 407 return moveExisting; 408 } 409 410 public void setMoveExisting(Expression moveExisting) { 411 this.moveExisting = moveExisting; 412 } 413 414 /** 415 * Sets the move existing expression based on 416 * {@link org.apache.camel.language.simple.SimpleLanguage} 417 */ 418 public void setMoveExisting(String fileLanguageExpression) { 419 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression); 420 this.moveExisting = createFileLanguageExpression(expression); 421 } 422 423 public Expression getFileName() { 424 return fileName; 425 } 426 427 public void setFileName(Expression fileName) { 428 this.fileName = fileName; 429 } 430 431 /** 432 * Sets the file expression based on 433 * {@link org.apache.camel.language.simple.SimpleLanguage} 434 */ 435 public void setFileName(String fileLanguageExpression) { 436 this.fileName = createFileLanguageExpression(fileLanguageExpression); 437 } 438 439 public String getDoneFileName() { 440 return doneFileName; 441 } 442 443 /** 444 * Sets the done file name. 445 * <p/> 446 * Only ${file.name} and ${file.name.noext} is supported as dynamic placeholders. 447 */ 448 public void setDoneFileName(String doneFileName) { 449 this.doneFileName = doneFileName; 450 } 451 452 public Boolean isIdempotent() { 453 return idempotent != null ? idempotent : false; 454 } 455 456 public String getCharset() { 457 return charset; 458 } 459 460 public void setCharset(String charset) { 461 IOHelper.validateCharset(charset); 462 this.charset = charset; 463 } 464 465 protected boolean isIdempotentSet() { 466 return idempotent != null; 467 } 468 469 public void setIdempotent(Boolean idempotent) { 470 this.idempotent = idempotent; 471 } 472 473 public Expression getIdempotentKey() { 474 return idempotentKey; 475 } 476 477 public void setIdempotentKey(Expression idempotentKey) { 478 this.idempotentKey = idempotentKey; 479 } 480 481 public void setIdempotentKey(String expression) { 482 this.idempotentKey = createFileLanguageExpression(expression); 483 } 484 485 public IdempotentRepository<String> getIdempotentRepository() { 486 return idempotentRepository; 487 } 488 489 public void setIdempotentRepository(IdempotentRepository<String> idempotentRepository) { 490 this.idempotentRepository = idempotentRepository; 491 } 492 493 public GenericFileFilter<T> getFilter() { 494 return filter; 495 } 496 497 public void setFilter(GenericFileFilter<T> filter) { 498 this.filter = filter; 499 } 500 501 public Comparator<GenericFile<T>> getSorter() { 502 return sorter; 503 } 504 505 public void setSorter(Comparator<GenericFile<T>> sorter) { 506 this.sorter = sorter; 507 } 508 509 public Comparator<Exchange> getSortBy() { 510 return sortBy; 511 } 512 513 public void setSortBy(Comparator<Exchange> sortBy) { 514 this.sortBy = sortBy; 515 } 516 517 public void setSortBy(String expression) { 518 setSortBy(expression, false); 519 } 520 521 public void setSortBy(String expression, boolean reverse) { 522 setSortBy(GenericFileDefaultSorter.sortByFileLanguage(getCamelContext(), expression, reverse)); 523 } 524 525 public String getTempPrefix() { 526 return tempPrefix; 527 } 528 529 /** 530 * Enables and uses temporary prefix when writing files, after write it will 531 * be renamed to the correct name. 532 */ 533 public void setTempPrefix(String tempPrefix) { 534 this.tempPrefix = tempPrefix; 535 // use only name as we set a prefix in from on the name 536 setTempFileName(tempPrefix + "${file:onlyname}"); 537 } 538 539 public Expression getTempFileName() { 540 return tempFileName; 541 } 542 543 public void setTempFileName(Expression tempFileName) { 544 this.tempFileName = tempFileName; 545 } 546 547 public void setTempFileName(String tempFileNameExpression) { 548 this.tempFileName = createFileLanguageExpression(tempFileNameExpression); 549 } 550 551 public boolean isEagerDeleteTargetFile() { 552 return eagerDeleteTargetFile; 553 } 554 555 public void setEagerDeleteTargetFile(boolean eagerDeleteTargetFile) { 556 this.eagerDeleteTargetFile = eagerDeleteTargetFile; 557 } 558 559 public GenericFileConfiguration getConfiguration() { 560 if (configuration == null) { 561 configuration = new GenericFileConfiguration(); 562 } 563 return configuration; 564 } 565 566 public void setConfiguration(GenericFileConfiguration configuration) { 567 this.configuration = configuration; 568 } 569 570 public GenericFileExclusiveReadLockStrategy<T> getExclusiveReadLockStrategy() { 571 return exclusiveReadLockStrategy; 572 } 573 574 public void setExclusiveReadLockStrategy(GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy) { 575 this.exclusiveReadLockStrategy = exclusiveReadLockStrategy; 576 } 577 578 public String getReadLock() { 579 return readLock; 580 } 581 582 public void setReadLock(String readLock) { 583 this.readLock = readLock; 584 } 585 586 public long getReadLockCheckInterval() { 587 return readLockCheckInterval; 588 } 589 590 public void setReadLockCheckInterval(long readLockCheckInterval) { 591 this.readLockCheckInterval = readLockCheckInterval; 592 } 593 594 public long getReadLockTimeout() { 595 return readLockTimeout; 596 } 597 598 public void setReadLockTimeout(long readLockTimeout) { 599 this.readLockTimeout = readLockTimeout; 600 } 601 602 public LoggingLevel getReadLockLoggingLevel() { 603 return readLockLoggingLevel; 604 } 605 606 public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) { 607 this.readLockLoggingLevel = readLockLoggingLevel; 608 } 609 610 public long getReadLockMinLength() { 611 return readLockMinLength; 612 } 613 614 public void setReadLockMinLength(long readLockMinLength) { 615 this.readLockMinLength = readLockMinLength; 616 } 617 618 public int getBufferSize() { 619 return bufferSize; 620 } 621 622 public void setBufferSize(int bufferSize) { 623 if (bufferSize <= 0) { 624 throw new IllegalArgumentException("BufferSize must be a positive value, was " + bufferSize); 625 } 626 this.bufferSize = bufferSize; 627 } 628 629 public GenericFileExist getFileExist() { 630 return fileExist; 631 } 632 633 public void setFileExist(GenericFileExist fileExist) { 634 this.fileExist = fileExist; 635 } 636 637 public boolean isAutoCreate() { 638 return autoCreate; 639 } 640 641 public void setAutoCreate(boolean autoCreate) { 642 this.autoCreate = autoCreate; 643 } 644 645 public boolean isStartingDirectoryMustExist() { 646 return startingDirectoryMustExist; 647 } 648 649 public void setStartingDirectoryMustExist(boolean startingDirectoryMustExist) { 650 this.startingDirectoryMustExist = startingDirectoryMustExist; 651 } 652 653 public boolean isDirectoryMustExist() { 654 return directoryMustExist; 655 } 656 657 public void setDirectoryMustExist(boolean directoryMustExist) { 658 this.directoryMustExist = directoryMustExist; 659 } 660 661 public GenericFileProcessStrategy<T> getProcessStrategy() { 662 return processStrategy; 663 } 664 665 public void setProcessStrategy(GenericFileProcessStrategy<T> processStrategy) { 666 this.processStrategy = processStrategy; 667 } 668 669 public String getLocalWorkDirectory() { 670 return localWorkDirectory; 671 } 672 673 public void setLocalWorkDirectory(String localWorkDirectory) { 674 this.localWorkDirectory = localWorkDirectory; 675 } 676 677 public int getMaxMessagesPerPoll() { 678 return maxMessagesPerPoll; 679 } 680 681 public void setMaxMessagesPerPoll(int maxMessagesPerPoll) { 682 this.maxMessagesPerPoll = maxMessagesPerPoll; 683 } 684 685 public boolean isEagerMaxMessagesPerPoll() { 686 return eagerMaxMessagesPerPoll; 687 } 688 689 public void setEagerMaxMessagesPerPoll(boolean eagerMaxMessagesPerPoll) { 690 this.eagerMaxMessagesPerPoll = eagerMaxMessagesPerPoll; 691 } 692 693 public int getMaxDepth() { 694 return maxDepth; 695 } 696 697 public void setMaxDepth(int maxDepth) { 698 this.maxDepth = maxDepth; 699 } 700 701 public int getMinDepth() { 702 return minDepth; 703 } 704 705 public void setMinDepth(int minDepth) { 706 this.minDepth = minDepth; 707 } 708 709 public IdempotentRepository<String> getInProgressRepository() { 710 return inProgressRepository; 711 } 712 713 public void setInProgressRepository(IdempotentRepository<String> inProgressRepository) { 714 this.inProgressRepository = inProgressRepository; 715 } 716 717 public boolean isKeepLastModified() { 718 return keepLastModified; 719 } 720 721 public void setKeepLastModified(boolean keepLastModified) { 722 this.keepLastModified = keepLastModified; 723 } 724 725 public boolean isAllowNullBody() { 726 return allowNullBody; 727 } 728 729 public void setAllowNullBody(boolean allowNullBody) { 730 this.allowNullBody = allowNullBody; 731 } 732 733 /** 734 * Configures the given message with the file which sets the body to the 735 * file object. 736 */ 737 public void configureMessage(GenericFile<T> file, Message message) { 738 message.setBody(file); 739 740 if (flatten) { 741 // when flatten the file name should not contain any paths 742 message.setHeader(Exchange.FILE_NAME, file.getFileNameOnly()); 743 } else { 744 // compute name to set on header that should be relative to starting directory 745 String name = file.isAbsolute() ? file.getAbsoluteFilePath() : file.getRelativeFilePath(); 746 747 // skip leading endpoint configured directory 748 String endpointPath = getConfiguration().getDirectory() + getFileSeparator(); 749 750 // need to normalize paths to ensure we can match using startsWith 751 endpointPath = FileUtil.normalizePath(endpointPath); 752 String copyOfName = FileUtil.normalizePath(name); 753 if (ObjectHelper.isNotEmpty(endpointPath) && copyOfName.startsWith(endpointPath)) { 754 name = name.substring(endpointPath.length()); 755 } 756 757 // adjust filename 758 message.setHeader(Exchange.FILE_NAME, name); 759 } 760 } 761 762 /** 763 * Set up the exchange properties with the options of the file endpoint 764 */ 765 public void configureExchange(Exchange exchange) { 766 // Now we just set the charset property here 767 if (getCharset() != null) { 768 exchange.setProperty(Exchange.CHARSET_NAME, getCharset()); 769 } 770 } 771 772 /** 773 * Strategy to configure the move, preMove, or moveExisting option based on a String input. 774 * 775 * @param expression the original string input 776 * @return configured string or the original if no modifications is needed 777 */ 778 protected String configureMoveOrPreMoveExpression(String expression) { 779 // if the expression already have ${ } placeholders then pass it unmodified 780 if (StringHelper.hasStartToken(expression, "simple")) { 781 return expression; 782 } 783 784 // remove trailing slash 785 expression = FileUtil.stripTrailingSeparator(expression); 786 787 StringBuilder sb = new StringBuilder(); 788 789 // if relative then insert start with the parent folder 790 if (!isAbsolute(expression)) { 791 sb.append("${file:parent}"); 792 sb.append(getFileSeparator()); 793 } 794 // insert the directory the end user provided 795 sb.append(expression); 796 // append only the filename (file:name can contain a relative path, so we must use onlyname) 797 sb.append(getFileSeparator()); 798 sb.append("${file:onlyname}"); 799 800 return sb.toString(); 801 } 802 803 protected Map<String, Object> getParamsAsMap() { 804 Map<String, Object> params = new HashMap<String, Object>(); 805 806 if (isNoop()) { 807 params.put("noop", Boolean.toString(true)); 808 } 809 if (isDelete()) { 810 params.put("delete", Boolean.toString(true)); 811 } 812 if (move != null) { 813 params.put("move", move); 814 } 815 if (moveFailed != null) { 816 params.put("moveFailed", moveFailed); 817 } 818 if (preMove != null) { 819 params.put("preMove", preMove); 820 } 821 if (exclusiveReadLockStrategy != null) { 822 params.put("exclusiveReadLockStrategy", exclusiveReadLockStrategy); 823 } 824 if (readLock != null) { 825 params.put("readLock", readLock); 826 } 827 if (readLockCheckInterval > 0) { 828 params.put("readLockCheckInterval", readLockCheckInterval); 829 } 830 if (readLockTimeout > 0) { 831 params.put("readLockTimeout", readLockTimeout); 832 } 833 params.put("readLockMinLength", readLockMinLength); 834 params.put("readLockLoggingLevel", readLockLoggingLevel); 835 836 return params; 837 } 838 839 private Expression createFileLanguageExpression(String expression) { 840 Language language; 841 // only use file language if the name is complex (eg. using $) 842 if (expression.contains("$")) { 843 language = getCamelContext().resolveLanguage("file"); 844 } else { 845 language = getCamelContext().resolveLanguage("constant"); 846 } 847 return language.createExpression(expression); 848 } 849 850 /** 851 * Creates the associated name of the done file based on the given file name. 852 * <p/> 853 * This method should only be invoked if a done filename property has been set on this endpoint. 854 * 855 * @param fileName the file name 856 * @return name of the associated done file name 857 */ 858 protected String createDoneFileName(String fileName) { 859 String pattern = getDoneFileName(); 860 ObjectHelper.notEmpty(pattern, "doneFileName", pattern); 861 862 // we only support ${file:name} or ${file:name.noext} as dynamic placeholders for done files 863 String path = FileUtil.onlyPath(fileName); 864 String onlyName = FileUtil.stripPath(fileName); 865 866 pattern = pattern.replaceFirst("\\$\\{file:name\\}", onlyName); 867 pattern = pattern.replaceFirst("\\$simple\\{file:name\\}", onlyName); 868 pattern = pattern.replaceFirst("\\$\\{file:name.noext\\}", FileUtil.stripExt(onlyName)); 869 pattern = pattern.replaceFirst("\\$simple\\{file:name.noext\\}", FileUtil.stripExt(onlyName)); 870 871 // must be able to resolve all placeholders supported 872 if (StringHelper.hasStartToken(pattern, "simple")) { 873 throw new ExpressionIllegalSyntaxException(fileName + ". Cannot resolve reminder: " + pattern); 874 } 875 876 String answer = pattern; 877 if (ObjectHelper.isNotEmpty(path) && ObjectHelper.isNotEmpty(pattern)) { 878 // done file must always be in same directory as the real file name 879 answer = path + getFileSeparator() + pattern; 880 } 881 882 if (getConfiguration().needToNormalize()) { 883 // must normalize path to cater for Windows and other OS 884 answer = FileUtil.normalizePath(answer); 885 } 886 887 return answer; 888 } 889 890 /** 891 * Is the given file a done file? 892 * <p/> 893 * This method should only be invoked if a done filename property has been set on this endpoint. 894 * 895 * @param fileName the file name 896 * @return <tt>true</tt> if its a done file, <tt>false</tt> otherwise 897 */ 898 protected boolean isDoneFile(String fileName) { 899 String pattern = getDoneFileName(); 900 ObjectHelper.notEmpty(pattern, "doneFileName", pattern); 901 902 if (!StringHelper.hasStartToken(pattern, "simple")) { 903 // no tokens, so just match names directly 904 return pattern.equals(fileName); 905 } 906 907 // the static part of the pattern, is that a prefix or suffix? 908 // its a prefix if ${ start token is not at the start of the pattern 909 boolean prefix = pattern.indexOf("${") > 0; 910 911 // remove dynamic parts of the pattern so we only got the static part left 912 pattern = pattern.replaceFirst("\\$\\{file:name\\}", ""); 913 pattern = pattern.replaceFirst("\\$simple\\{file:name\\}", ""); 914 pattern = pattern.replaceFirst("\\$\\{file:name.noext\\}", ""); 915 pattern = pattern.replaceFirst("\\$simple\\{file:name.noext\\}", ""); 916 917 // must be able to resolve all placeholders supported 918 if (StringHelper.hasStartToken(pattern, "simple")) { 919 throw new ExpressionIllegalSyntaxException(fileName + ". Cannot resolve reminder: " + pattern); 920 } 921 922 if (prefix) { 923 return fileName.startsWith(pattern); 924 } else { 925 return fileName.endsWith(pattern); 926 } 927 } 928 929 @Override 930 protected void doStart() throws Exception { 931 ServiceHelper.startServices(inProgressRepository, idempotentRepository); 932 super.doStart(); 933 } 934 935 @Override 936 protected void doStop() throws Exception { 937 super.doStop(); 938 ServiceHelper.stopServices(inProgressRepository, idempotentRepository); 939 } 940 }