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