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