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