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;
026
027import org.apache.camel.CamelContext;
028import org.apache.camel.Component;
029import org.apache.camel.Exchange;
030import org.apache.camel.Expression;
031import org.apache.camel.ExpressionIllegalSyntaxException;
032import org.apache.camel.LoggingLevel;
033import org.apache.camel.Message;
034import org.apache.camel.Processor;
035import org.apache.camel.impl.ScheduledPollEndpoint;
036import org.apache.camel.processor.idempotent.MemoryIdempotentRepository;
037import org.apache.camel.spi.BrowsableEndpoint;
038import org.apache.camel.spi.FactoryFinder;
039import org.apache.camel.spi.IdempotentRepository;
040import org.apache.camel.spi.Language;
041import org.apache.camel.spi.UriParam;
042import org.apache.camel.util.FileUtil;
043import org.apache.camel.util.IOHelper;
044import org.apache.camel.util.ObjectHelper;
045import org.apache.camel.util.ServiceHelper;
046import org.apache.camel.util.StringHelper;
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049
050/**
051 * Base class for file endpoints
052 */
053public abstract class GenericFileEndpoint<T> extends ScheduledPollEndpoint implements BrowsableEndpoint {
054
055    protected static final String DEFAULT_STRATEGYFACTORY_CLASS = "org.apache.camel.component.file.strategy.GenericFileProcessStrategyFactory";
056    protected static final int DEFAULT_IDEMPOTENT_CACHE_SIZE = 1000;
057
058    protected final Logger log = LoggerFactory.getLogger(getClass());
059
060    protected GenericFileConfiguration configuration;
061
062    @UriParam
063    protected GenericFileProcessStrategy<T> processStrategy;
064    @UriParam
065    protected IdempotentRepository<String> inProgressRepository = new MemoryIdempotentRepository();
066    @UriParam
067    protected String localWorkDirectory;
068    @UriParam
069    protected boolean autoCreate = true;
070    @UriParam
071    protected boolean startingDirectoryMustExist;
072    @UriParam
073    protected boolean directoryMustExist;
074    @UriParam
075    protected int bufferSize = FileUtil.BUFFER_SIZE;
076    @UriParam
077    protected GenericFileExist fileExist = GenericFileExist.Override;
078    @UriParam
079    protected boolean noop;
080    @UriParam
081    protected boolean recursive;
082    @UriParam
083    protected boolean delete;
084    @UriParam
085    protected boolean flatten;
086    @UriParam
087    protected int maxMessagesPerPoll;
088    @UriParam
089    protected boolean eagerMaxMessagesPerPoll = true;
090    @UriParam
091    protected int maxDepth = Integer.MAX_VALUE;
092    @UriParam
093    protected int minDepth;
094    @UriParam
095    protected String tempPrefix;
096    @UriParam
097    protected Expression tempFileName;
098    @UriParam
099    protected boolean eagerDeleteTargetFile = true;
100    @UriParam
101    protected String include;
102    @UriParam
103    protected String exclude;
104    @UriParam
105    protected String charset;
106    @UriParam
107    protected Expression fileName;
108    @UriParam
109    protected Expression move;
110    @UriParam
111    protected Expression moveFailed;
112    @UriParam
113    protected Expression preMove;
114    @UriParam
115    protected Expression moveExisting;
116    @UriParam
117    protected Boolean idempotent;
118    @UriParam
119    protected Expression idempotentKey;
120    @UriParam
121    protected IdempotentRepository<String> idempotentRepository;
122    @UriParam
123    protected GenericFileFilter<T> filter;
124    @UriParam
125    protected AntPathMatcherGenericFileFilter<T> antFilter;
126    @UriParam
127    protected Comparator<GenericFile<T>> sorter;
128    @UriParam
129    protected Comparator<Exchange> sortBy;
130    @UriParam
131    protected String readLock = "none";
132    @UriParam
133    protected long readLockCheckInterval = 1000;
134    @UriParam
135    protected long readLockTimeout = 10000;
136    @UriParam
137    protected boolean readLockMarkerFile = true;
138    @UriParam
139    protected LoggingLevel readLockLoggingLevel = LoggingLevel.WARN;
140    @UriParam
141    protected long readLockMinLength = 1;
142    @UriParam
143    protected GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy;
144    @UriParam
145    protected boolean keepLastModified;
146    @UriParam
147    protected String doneFileName;
148    @UriParam
149    protected boolean allowNullBody;
150
151    public GenericFileEndpoint() {
152    }
153
154    public GenericFileEndpoint(String endpointUri, Component component) {
155        super(endpointUri, component);
156    }
157
158    public boolean isSingleton() {
159        return true;
160    }
161
162    public abstract GenericFileConsumer<T> createConsumer(Processor processor) throws Exception;
163
164    public abstract GenericFileProducer<T> createProducer() throws Exception;
165
166    public abstract Exchange createExchange(GenericFile<T> file);
167
168    public abstract String getScheme();
169
170    public abstract char getFileSeparator();
171
172    public abstract boolean isAbsolute(String name);
173
174    /**
175     * Return the file name that will be auto-generated for the given message if
176     * none is provided
177     */
178    public String getGeneratedFileName(Message message) {
179        return StringHelper.sanitize(message.getMessageId());
180    }
181
182    public GenericFileProcessStrategy<T> getGenericFileProcessStrategy() {
183        if (processStrategy == null) {
184            processStrategy = createGenericFileStrategy();
185            log.debug("Using Generic file process strategy: {}", processStrategy);
186        }
187        return processStrategy;
188    }
189
190    /**
191     * This implementation will <b>not</b> load the file content.
192     * Any file locking is neither in use by this implementation..
193     */
194    @Override
195    public List<Exchange> getExchanges() {
196        final List<Exchange> answer = new ArrayList<Exchange>();
197
198        GenericFileConsumer<?> consumer = null;
199        try {
200            // create a new consumer which can poll the exchanges we want to browse
201            // do not provide a processor as we do some custom processing
202            consumer = createConsumer(null);
203            consumer.setCustomProcessor(new Processor() {
204                @Override
205                public void process(Exchange exchange) throws Exception {
206                    answer.add(exchange);
207                }
208            });
209            // do not start scheduler, as we invoke the poll manually
210            consumer.setStartScheduler(false);
211            // start consumer
212            ServiceHelper.startService(consumer);
213            // invoke poll which performs the custom processing, so we can browse the exchanges
214            consumer.poll();
215        } catch (Exception e) {
216            throw ObjectHelper.wrapRuntimeCamelException(e);
217        } finally {
218            try {
219                ServiceHelper.stopService(consumer);
220            } catch (Exception e) {
221                log.debug("Error stopping consumer used for browsing exchanges. This exception will be ignored", e);
222            }
223        }
224
225        return answer;
226    }
227
228    /**
229     * A strategy method to lazily create the file strategy
230     */
231    @SuppressWarnings("unchecked")
232    protected GenericFileProcessStrategy<T> createGenericFileStrategy() {
233        Class<?> factory = null;
234        try {
235            FactoryFinder finder = getCamelContext().getFactoryFinder("META-INF/services/org/apache/camel/component/");
236            log.trace("Using FactoryFinder: {}", finder);
237            factory = finder.findClass(getScheme(), "strategy.factory.", CamelContext.class);
238        } catch (ClassNotFoundException e) {
239            log.trace("'strategy.factory.class' not found", e);
240        } catch (IOException e) {
241            log.trace("No strategy factory defined in 'META-INF/services/org/apache/camel/component/'", e);
242        }
243
244        if (factory == null) {
245            // use default
246            try {
247                log.trace("Using ClassResolver to resolve class: {}", DEFAULT_STRATEGYFACTORY_CLASS);
248                factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS);
249            } catch (Exception e) {
250                log.trace("Cannot load class: {}", DEFAULT_STRATEGYFACTORY_CLASS, e);
251            }
252            // fallback and us this class loader
253            try {
254                if (log.isTraceEnabled()) {
255                    log.trace("Using classloader: {} to resolve class: {}", this.getClass().getClassLoader(), DEFAULT_STRATEGYFACTORY_CLASS);
256                }
257                factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS, this.getClass().getClassLoader());
258            } catch (Exception e) {
259                if (log.isTraceEnabled()) {
260                    log.trace("Cannot load class: {} using classloader: " + this.getClass().getClassLoader(), DEFAULT_STRATEGYFACTORY_CLASS, e);
261                }
262            }
263
264            if (factory == null) {
265                throw new TypeNotPresentException(DEFAULT_STRATEGYFACTORY_CLASS + " class not found", null);
266            }
267        }
268
269        try {
270            Method factoryMethod = factory.getMethod("createGenericFileProcessStrategy", CamelContext.class, Map.class);
271            Map<String, Object> params = getParamsAsMap();
272            log.debug("Parameters for Generic file process strategy {}", params);
273            return (GenericFileProcessStrategy<T>) ObjectHelper.invokeMethod(factoryMethod, null, getCamelContext(), params);
274        } catch (NoSuchMethodException e) {
275            throw new TypeNotPresentException(factory.getSimpleName() + ".createGenericFileProcessStrategy method not found", e);
276        }
277    }
278
279    public boolean isNoop() {
280        return noop;
281    }
282
283    public void setNoop(boolean noop) {
284        this.noop = noop;
285    }
286
287    public boolean isRecursive() {
288        return recursive;
289    }
290
291    public void setRecursive(boolean recursive) {
292        this.recursive = recursive;
293    }
294
295    public String getInclude() {
296        return include;
297    }
298
299    public void setInclude(String include) {
300        this.include = include;
301    }
302
303    public String getExclude() {
304        return exclude;
305    }
306
307    public void setExclude(String exclude) {
308        this.exclude = exclude;
309    }
310
311    public void setAntInclude(String antInclude) {
312        if (this.antFilter == null) {
313            this.antFilter = new AntPathMatcherGenericFileFilter<T>();
314        }
315        this.antFilter.setIncludes(antInclude);
316    }
317
318    public void setAntExclude(String antExclude) {
319        if (this.antFilter == null) {
320            this.antFilter = new AntPathMatcherGenericFileFilter<T>();
321        }
322        this.antFilter.setExcludes(antExclude);
323    }
324
325    /**
326     * Sets case sensitive flag on {@link org.apache.camel.component.file.AntPathMatcherFileFilter}
327     * <p/>
328     * Is by default turned on <tt>true</tt>.
329     */
330    public void setAntFilterCaseSensitive(boolean antFilterCaseSensitive) {
331        if (this.antFilter == null) {
332            this.antFilter = new AntPathMatcherGenericFileFilter<T>();
333        }
334        this.antFilter.setCaseSensitive(antFilterCaseSensitive);
335    }
336
337    public GenericFileFilter<T> getAntFilter() {
338        return antFilter;
339    }
340
341    public boolean isDelete() {
342        return delete;
343    }
344
345    public void setDelete(boolean delete) {
346        this.delete = delete;
347    }
348
349    public boolean isFlatten() {
350        return flatten;
351    }
352
353    public void setFlatten(boolean flatten) {
354        this.flatten = flatten;
355    }
356
357    public Expression getMove() {
358        return move;
359    }
360
361    public void setMove(Expression move) {
362        this.move = move;
363    }
364
365    /**
366     * Sets the move failure expression based on
367     * {@link org.apache.camel.language.simple.SimpleLanguage}
368     */
369    public void setMoveFailed(String fileLanguageExpression) {
370        String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
371        this.moveFailed = createFileLanguageExpression(expression);
372    }
373
374    public Expression getMoveFailed() {
375        return moveFailed;
376    }
377
378    public void setMoveFailed(Expression moveFailed) {
379        this.moveFailed = moveFailed;
380    }
381
382    /**
383     * Sets the move expression based on
384     * {@link org.apache.camel.language.simple.SimpleLanguage}
385     */
386    public void setMove(String fileLanguageExpression) {
387        String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
388        this.move = createFileLanguageExpression(expression);
389    }
390
391    public Expression getPreMove() {
392        return preMove;
393    }
394
395    public void setPreMove(Expression preMove) {
396        this.preMove = preMove;
397    }
398
399    /**
400     * Sets the pre move expression based on
401     * {@link org.apache.camel.language.simple.SimpleLanguage}
402     */
403    public void setPreMove(String fileLanguageExpression) {
404        String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
405        this.preMove = createFileLanguageExpression(expression);
406    }
407
408    public Expression getMoveExisting() {
409        return moveExisting;
410    }
411
412    public void setMoveExisting(Expression moveExisting) {
413        this.moveExisting = moveExisting;
414    }
415
416    /**
417     * Sets the move existing expression based on
418     * {@link org.apache.camel.language.simple.SimpleLanguage}
419     */
420    public void setMoveExisting(String fileLanguageExpression) {
421        String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
422        this.moveExisting = createFileLanguageExpression(expression);
423    }
424
425    public Expression getFileName() {
426        return fileName;
427    }
428
429    public void setFileName(Expression fileName) {
430        this.fileName = fileName;
431    }
432
433    /**
434     * Sets the file expression based on
435     * {@link org.apache.camel.language.simple.SimpleLanguage}
436     */
437    public void setFileName(String fileLanguageExpression) {
438        this.fileName = createFileLanguageExpression(fileLanguageExpression);
439    }
440
441    public String getDoneFileName() {
442        return doneFileName;
443    }
444
445    /**
446     * Sets the done file name.
447     * <p/>
448     * Only ${file.name} and ${file.name.noext} is supported as dynamic placeholders.
449     */
450    public void setDoneFileName(String doneFileName) {
451        this.doneFileName = doneFileName;
452    }
453
454    public Boolean isIdempotent() {
455        return idempotent != null ? idempotent : false;
456    }
457
458    public String getCharset() {
459        return charset;
460    }
461
462    public void setCharset(String charset) {
463        IOHelper.validateCharset(charset);
464        this.charset = charset;
465    }
466
467    protected boolean isIdempotentSet() {
468        return idempotent != null;
469    }
470
471    public void setIdempotent(Boolean idempotent) {
472        this.idempotent = idempotent;
473    }
474
475    public Expression getIdempotentKey() {
476        return idempotentKey;
477    }
478
479    public void setIdempotentKey(Expression idempotentKey) {
480        this.idempotentKey = idempotentKey;
481    }
482
483    public void setIdempotentKey(String expression) {
484        this.idempotentKey = createFileLanguageExpression(expression);
485    }
486
487    public IdempotentRepository<String> getIdempotentRepository() {
488        return idempotentRepository;
489    }
490
491    public void setIdempotentRepository(IdempotentRepository<String> idempotentRepository) {
492        this.idempotentRepository = idempotentRepository;
493    }
494
495    public GenericFileFilter<T> getFilter() {
496        return filter;
497    }
498
499    public void setFilter(GenericFileFilter<T> filter) {
500        this.filter = filter;
501    }
502
503    public Comparator<GenericFile<T>> getSorter() {
504        return sorter;
505    }
506
507    public void setSorter(Comparator<GenericFile<T>> sorter) {
508        this.sorter = sorter;
509    }
510
511    public Comparator<Exchange> getSortBy() {
512        return sortBy;
513    }
514
515    public void setSortBy(Comparator<Exchange> sortBy) {
516        this.sortBy = sortBy;
517    }
518
519    public void setSortBy(String expression) {
520        setSortBy(expression, false);
521    }
522
523    public void setSortBy(String expression, boolean reverse) {
524        setSortBy(GenericFileDefaultSorter.sortByFileLanguage(getCamelContext(), expression, reverse));
525    }
526
527    public String getTempPrefix() {
528        return tempPrefix;
529    }
530
531    /**
532     * Enables and uses temporary prefix when writing files, after write it will
533     * be renamed to the correct name.
534     */
535    public void setTempPrefix(String tempPrefix) {
536        this.tempPrefix = tempPrefix;
537        // use only name as we set a prefix in from on the name
538        setTempFileName(tempPrefix + "${file:onlyname}");
539    }
540
541    public Expression getTempFileName() {
542        return tempFileName;
543    }
544
545    public void setTempFileName(Expression tempFileName) {
546        this.tempFileName = tempFileName;
547    }
548
549    public void setTempFileName(String tempFileNameExpression) {
550        this.tempFileName = createFileLanguageExpression(tempFileNameExpression);
551    }
552
553    public boolean isEagerDeleteTargetFile() {
554        return eagerDeleteTargetFile;
555    }
556
557    public void setEagerDeleteTargetFile(boolean eagerDeleteTargetFile) {
558        this.eagerDeleteTargetFile = eagerDeleteTargetFile;
559    }
560
561    public GenericFileConfiguration getConfiguration() {
562        if (configuration == null) {
563            configuration = new GenericFileConfiguration();
564        }
565        return configuration;
566    }
567
568    public void setConfiguration(GenericFileConfiguration configuration) {
569        this.configuration = configuration;
570    }
571
572    public GenericFileExclusiveReadLockStrategy<T> getExclusiveReadLockStrategy() {
573        return exclusiveReadLockStrategy;
574    }
575
576    public void setExclusiveReadLockStrategy(GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy) {
577        this.exclusiveReadLockStrategy = exclusiveReadLockStrategy;
578    }
579
580    public String getReadLock() {
581        return readLock;
582    }
583
584    public void setReadLock(String readLock) {
585        this.readLock = readLock;
586    }
587
588    public long getReadLockCheckInterval() {
589        return readLockCheckInterval;
590    }
591
592    public void setReadLockCheckInterval(long readLockCheckInterval) {
593        this.readLockCheckInterval = readLockCheckInterval;
594    }
595
596    public long getReadLockTimeout() {
597        return readLockTimeout;
598    }
599
600    public void setReadLockTimeout(long readLockTimeout) {
601        this.readLockTimeout = readLockTimeout;
602    }
603
604    public boolean isReadLockMarkerFile() {
605        return readLockMarkerFile;
606    }
607
608    public void setReadLockMarkerFile(boolean readLockMarkerFile) {
609        this.readLockMarkerFile = readLockMarkerFile;
610    }
611
612    public LoggingLevel getReadLockLoggingLevel() {
613        return readLockLoggingLevel;
614    }
615
616    public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) {
617        this.readLockLoggingLevel = readLockLoggingLevel;
618    }
619
620    public long getReadLockMinLength() {
621        return readLockMinLength;
622    }
623
624    public void setReadLockMinLength(long readLockMinLength) {
625        this.readLockMinLength = readLockMinLength;
626    }
627
628    public int getBufferSize() {
629        return bufferSize;
630    }
631
632    public void setBufferSize(int bufferSize) {
633        if (bufferSize <= 0) {
634            throw new IllegalArgumentException("BufferSize must be a positive value, was " + bufferSize);
635        }
636        this.bufferSize = bufferSize;
637    }
638
639    public GenericFileExist getFileExist() {
640        return fileExist;
641    }
642
643    public void setFileExist(GenericFileExist fileExist) {
644        this.fileExist = fileExist;
645    }
646
647    public boolean isAutoCreate() {
648        return autoCreate;
649    }
650
651    public void setAutoCreate(boolean autoCreate) {
652        this.autoCreate = autoCreate;
653    }
654
655    public boolean isStartingDirectoryMustExist() {
656        return startingDirectoryMustExist;
657    }
658
659    public void setStartingDirectoryMustExist(boolean startingDirectoryMustExist) {
660        this.startingDirectoryMustExist = startingDirectoryMustExist;
661    }
662
663    public boolean isDirectoryMustExist() {
664        return directoryMustExist;
665    }
666
667    public void setDirectoryMustExist(boolean directoryMustExist) {
668        this.directoryMustExist = directoryMustExist;
669    }
670
671    public GenericFileProcessStrategy<T> getProcessStrategy() {
672        return processStrategy;
673    }
674
675    public void setProcessStrategy(GenericFileProcessStrategy<T> processStrategy) {
676        this.processStrategy = processStrategy;
677    }
678
679    public String getLocalWorkDirectory() {
680        return localWorkDirectory;
681    }
682
683    public void setLocalWorkDirectory(String localWorkDirectory) {
684        this.localWorkDirectory = localWorkDirectory;
685    }
686
687    public int getMaxMessagesPerPoll() {
688        return maxMessagesPerPoll;
689    }
690
691    public void setMaxMessagesPerPoll(int maxMessagesPerPoll) {
692        this.maxMessagesPerPoll = maxMessagesPerPoll;
693    }
694
695    public boolean isEagerMaxMessagesPerPoll() {
696        return eagerMaxMessagesPerPoll;
697    }
698
699    public void setEagerMaxMessagesPerPoll(boolean eagerMaxMessagesPerPoll) {
700        this.eagerMaxMessagesPerPoll = eagerMaxMessagesPerPoll;
701    }
702
703    public int getMaxDepth() {
704        return maxDepth;
705    }
706
707    public void setMaxDepth(int maxDepth) {
708        this.maxDepth = maxDepth;
709    }
710
711    public int getMinDepth() {
712        return minDepth;
713    }
714
715    public void setMinDepth(int minDepth) {
716        this.minDepth = minDepth;
717    }
718
719    public IdempotentRepository<String> getInProgressRepository() {
720        return inProgressRepository;
721    }
722
723    public void setInProgressRepository(IdempotentRepository<String> inProgressRepository) {
724        this.inProgressRepository = inProgressRepository;
725    }
726
727    public boolean isKeepLastModified() {
728        return keepLastModified;
729    }
730
731    public void setKeepLastModified(boolean keepLastModified) {
732        this.keepLastModified = keepLastModified;
733    }
734
735    public boolean isAllowNullBody() {
736        return allowNullBody;
737    }
738    
739    public void setAllowNullBody(boolean allowNullBody) {
740        this.allowNullBody = allowNullBody;
741    }
742    
743    /**
744     * Configures the given message with the file which sets the body to the
745     * file object.
746     */
747    public void configureMessage(GenericFile<T> file, Message message) {
748        message.setBody(file);
749
750        if (flatten) {
751            // when flatten the file name should not contain any paths
752            message.setHeader(Exchange.FILE_NAME, file.getFileNameOnly());
753        } else {
754            // compute name to set on header that should be relative to starting directory
755            String name = file.isAbsolute() ? file.getAbsoluteFilePath() : file.getRelativeFilePath();
756
757            // skip leading endpoint configured directory
758            String endpointPath = getConfiguration().getDirectory() + getFileSeparator();
759
760            // need to normalize paths to ensure we can match using startsWith
761            endpointPath = FileUtil.normalizePath(endpointPath);
762            String copyOfName = FileUtil.normalizePath(name);
763            if (ObjectHelper.isNotEmpty(endpointPath) && copyOfName.startsWith(endpointPath)) {
764                name = name.substring(endpointPath.length());
765            }
766
767            // adjust filename
768            message.setHeader(Exchange.FILE_NAME, name);
769        }
770    }
771
772    /**
773     * Set up the exchange properties with the options of the file endpoint
774     */
775    public void configureExchange(Exchange exchange) {
776        // Now we just set the charset property here
777        if (getCharset() != null) {
778            exchange.setProperty(Exchange.CHARSET_NAME, getCharset());
779        }
780    }
781
782    /**
783     * Strategy to configure the move, preMove, or moveExisting option based on a String input.
784     *
785     * @param expression the original string input
786     * @return configured string or the original if no modifications is needed
787     */
788    protected String configureMoveOrPreMoveExpression(String expression) {
789        // if the expression already have ${ } placeholders then pass it unmodified
790        if (StringHelper.hasStartToken(expression, "simple")) {
791            return expression;
792        }
793
794        // remove trailing slash
795        expression = FileUtil.stripTrailingSeparator(expression);
796
797        StringBuilder sb = new StringBuilder();
798
799        // if relative then insert start with the parent folder
800        if (!isAbsolute(expression)) {
801            sb.append("${file:parent}");
802            sb.append(getFileSeparator());
803        }
804        // insert the directory the end user provided
805        sb.append(expression);
806        // append only the filename (file:name can contain a relative path, so we must use onlyname)
807        sb.append(getFileSeparator());
808        sb.append("${file:onlyname}");
809
810        return sb.toString();
811    }
812
813    protected Map<String, Object> getParamsAsMap() {
814        Map<String, Object> params = new HashMap<String, Object>();
815
816        if (isNoop()) {
817            params.put("noop", Boolean.toString(true));
818        }
819        if (isDelete()) {
820            params.put("delete", Boolean.toString(true));
821        }
822        if (move != null) {
823            params.put("move", move);
824        }
825        if (moveFailed != null) {
826            params.put("moveFailed", moveFailed);
827        }
828        if (preMove != null) {
829            params.put("preMove", preMove);
830        }
831        if (exclusiveReadLockStrategy != null) {
832            params.put("exclusiveReadLockStrategy", exclusiveReadLockStrategy);
833        }
834        if (readLock != null) {
835            params.put("readLock", readLock);
836        }
837        if (readLockCheckInterval > 0) {
838            params.put("readLockCheckInterval", readLockCheckInterval);
839        }
840        if (readLockTimeout > 0) {
841            params.put("readLockTimeout", readLockTimeout);
842        }
843        params.put("readLockMarkerFile", readLockMarkerFile);
844        params.put("readLockMinLength", readLockMinLength);
845        params.put("readLockLoggingLevel", readLockLoggingLevel);
846
847        return params;
848    }
849
850    private Expression createFileLanguageExpression(String expression) {
851        Language language;
852        // only use file language if the name is complex (eg. using $)
853        if (expression.contains("$")) {
854            language = getCamelContext().resolveLanguage("file");
855        } else {
856            language = getCamelContext().resolveLanguage("constant");
857        }
858        return language.createExpression(expression);
859    }
860
861    /**
862     * Creates the associated name of the done file based on the given file name.
863     * <p/>
864     * This method should only be invoked if a done filename property has been set on this endpoint.
865     *
866     * @param fileName the file name
867     * @return name of the associated done file name
868     */
869    protected String createDoneFileName(String fileName) {
870        String pattern = getDoneFileName();
871        ObjectHelper.notEmpty(pattern, "doneFileName", pattern);
872
873        // we only support ${file:name} or ${file:name.noext} as dynamic placeholders for done files
874        String path = FileUtil.onlyPath(fileName);
875        String onlyName = FileUtil.stripPath(fileName);
876
877        pattern = pattern.replaceFirst("\\$\\{file:name\\}", onlyName);
878        pattern = pattern.replaceFirst("\\$simple\\{file:name\\}", onlyName);
879        pattern = pattern.replaceFirst("\\$\\{file:name.noext\\}", FileUtil.stripExt(onlyName));
880        pattern = pattern.replaceFirst("\\$simple\\{file:name.noext\\}", FileUtil.stripExt(onlyName));
881
882        // must be able to resolve all placeholders supported
883        if (StringHelper.hasStartToken(pattern, "simple")) {
884            throw new ExpressionIllegalSyntaxException(fileName + ". Cannot resolve reminder: " + pattern);
885        }
886
887        String answer = pattern;
888        if (ObjectHelper.isNotEmpty(path) && ObjectHelper.isNotEmpty(pattern)) {
889            // done file must always be in same directory as the real file name
890            answer = path + getFileSeparator() + pattern;
891        }
892
893        if (getConfiguration().needToNormalize()) {
894            // must normalize path to cater for Windows and other OS
895            answer = FileUtil.normalizePath(answer);
896        }
897
898        return answer;
899    }
900
901    /**
902     * Is the given file a done file?
903     * <p/>
904     * This method should only be invoked if a done filename property has been set on this endpoint.
905     *
906     * @param fileName the file name
907     * @return <tt>true</tt> if its a done file, <tt>false</tt> otherwise
908     */
909    protected boolean isDoneFile(String fileName) {
910        String pattern = getDoneFileName();
911        ObjectHelper.notEmpty(pattern, "doneFileName", pattern);
912
913        if (!StringHelper.hasStartToken(pattern, "simple")) {
914            // no tokens, so just match names directly
915            return pattern.equals(fileName);
916        }
917
918        // the static part of the pattern, is that a prefix or suffix?
919        // its a prefix if ${ start token is not at the start of the pattern
920        boolean prefix = pattern.indexOf("${") > 0;
921
922        // remove dynamic parts of the pattern so we only got the static part left
923        pattern = pattern.replaceFirst("\\$\\{file:name\\}", "");
924        pattern = pattern.replaceFirst("\\$simple\\{file:name\\}", "");
925        pattern = pattern.replaceFirst("\\$\\{file:name.noext\\}", "");
926        pattern = pattern.replaceFirst("\\$simple\\{file:name.noext\\}", "");
927
928        // must be able to resolve all placeholders supported
929        if (StringHelper.hasStartToken(pattern, "simple")) {
930            throw new ExpressionIllegalSyntaxException(fileName + ". Cannot resolve reminder: " + pattern);
931        }
932
933        if (prefix) {
934            return fileName.startsWith(pattern);
935        } else {
936            return fileName.endsWith(pattern);
937        }
938    }
939
940    @Override
941    protected void doStart() throws Exception {
942        ServiceHelper.startServices(inProgressRepository, idempotentRepository);
943        super.doStart();
944    }
945
946    @Override
947    protected void doStop() throws Exception {
948        super.doStop();
949        ServiceHelper.stopServices(inProgressRepository, idempotentRepository);
950    }
951}