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