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