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