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