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