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.util.ArrayList;
020import java.util.Collections;
021import java.util.Deque;
022import java.util.LinkedList;
023import java.util.List;
024import java.util.Queue;
025import java.util.regex.Pattern;
026
027import org.apache.camel.AsyncCallback;
028import org.apache.camel.Exchange;
029import org.apache.camel.Message;
030import org.apache.camel.Processor;
031import org.apache.camel.ShutdownRunningTask;
032import org.apache.camel.impl.ScheduledBatchPollingConsumer;
033import org.apache.camel.util.CastUtils;
034import org.apache.camel.util.ObjectHelper;
035import org.apache.camel.util.StopWatch;
036import org.apache.camel.util.StringHelper;
037import org.apache.camel.util.TimeUtils;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * Base class for file consumers.
043 */
044public abstract class GenericFileConsumer<T> extends ScheduledBatchPollingConsumer {
045    protected final Logger log = LoggerFactory.getLogger(getClass());
046    protected GenericFileEndpoint<T> endpoint;
047    protected GenericFileOperations<T> operations;
048    protected String fileExpressionResult;
049    protected volatile ShutdownRunningTask shutdownRunningTask;
050    protected volatile int pendingExchanges;
051    protected Processor customProcessor;
052    protected boolean eagerLimitMaxMessagesPerPoll = true;
053    protected volatile boolean prepareOnStartup;
054    private final Pattern includePattern;
055    private final Pattern excludePattern;
056
057    public GenericFileConsumer(GenericFileEndpoint<T> endpoint, Processor processor, GenericFileOperations<T> operations) {
058        super(endpoint, processor);
059        this.endpoint = endpoint;
060        this.operations = operations;
061
062        this.includePattern = endpoint.getIncludePattern();
063        this.excludePattern = endpoint.getExcludePattern();
064    }
065
066    public Processor getCustomProcessor() {
067        return customProcessor;
068    }
069
070    /**
071     * Use a custom processor to process the exchange.
072     * <p/>
073     * Only set this if you need to do custom processing, instead of the regular processing.
074     * <p/>
075     * This is for example used to browse file endpoints by leveraging the file consumer to poll
076     * the directory to gather the list of exchanges. But to avoid processing the files regularly
077     * we can use a custom processor.
078     *
079     * @param processor a custom processor
080     */
081    public void setCustomProcessor(Processor processor) {
082        this.customProcessor = processor;
083    }
084
085    public boolean isEagerLimitMaxMessagesPerPoll() {
086        return eagerLimitMaxMessagesPerPoll;
087    }
088
089    public void setEagerLimitMaxMessagesPerPoll(boolean eagerLimitMaxMessagesPerPoll) {
090        this.eagerLimitMaxMessagesPerPoll = eagerLimitMaxMessagesPerPoll;
091    }
092
093    /**
094     * Poll for files
095     */
096    protected int poll() throws Exception {
097        // must prepare on startup the very first time
098        if (!prepareOnStartup) {
099            // prepare on startup
100            endpoint.getGenericFileProcessStrategy().prepareOnStartup(operations, endpoint);
101            prepareOnStartup = true;
102        }
103
104        // must reset for each poll
105        fileExpressionResult = null;
106        shutdownRunningTask = null;
107        pendingExchanges = 0;
108
109        // before we poll is there anything we need to check?
110        // such as are we connected to the FTP Server still?
111        if (!prePollCheck()) {
112            log.debug("Skipping poll as pre poll check returned false");
113            return 0;
114        }
115
116        // gather list of files to process
117        List<GenericFile<T>> files = new ArrayList<GenericFile<T>>();
118        String name = endpoint.getConfiguration().getDirectory();
119
120        // time how long it takes to poll
121        StopWatch stop = new StopWatch();
122        boolean limitHit;
123        try {
124            limitHit = !pollDirectory(name, files, 0);
125        } catch (Exception e) {
126            // during poll directory we add files to the in progress repository, in case of any exception thrown after this work
127            // we must then drain the in progress files before rethrowing the exception
128            log.debug("Error occurred during poll directory: " + name + " due " + e.getMessage() + ". Removing " + files.size() + " files marked as in-progress.");
129            removeExcessiveInProgressFiles(files);
130            throw e;
131        }
132
133        long delta = stop.stop();
134        if (log.isDebugEnabled()) {
135            log.debug("Took {} to poll: {}", TimeUtils.printDuration(delta), name);
136        }
137
138        // log if we hit the limit
139        if (limitHit) {
140            log.debug("Limiting maximum messages to poll at {} files as there were more messages in this poll.", maxMessagesPerPoll);
141        }
142
143        // sort files using file comparator if provided
144        if (endpoint.getSorter() != null) {
145            files.sort(endpoint.getSorter());
146        }
147
148        // sort using build in sorters so we can use expressions
149        // use a linked list so we can dequeue the exchanges
150        LinkedList<Exchange> exchanges = new LinkedList<Exchange>();
151        for (GenericFile<T> file : files) {
152            Exchange exchange = endpoint.createExchange(file);
153            endpoint.configureExchange(exchange);
154            endpoint.configureMessage(file, exchange.getIn());
155            exchanges.add(exchange);
156        }
157        // sort files using exchange comparator if provided
158        if (endpoint.getSortBy() != null) {
159            exchanges.sort(endpoint.getSortBy());
160        }
161        if (endpoint.isShuffle()) {
162            Collections.shuffle(exchanges);
163        }
164
165        // use a queue for the exchanges
166        Deque<Exchange> q = exchanges;
167
168        // we are not eager limiting, but we have configured a limit, so cut the list of files
169        if (!eagerLimitMaxMessagesPerPoll && maxMessagesPerPoll > 0) {
170            if (files.size() > maxMessagesPerPoll) {
171                log.debug("Limiting maximum messages to poll at {} files as there were more messages in this poll.", maxMessagesPerPoll);
172                // must first remove excessive files from the in progress repository
173                removeExcessiveInProgressFiles(q, maxMessagesPerPoll);
174            }
175        }
176
177        // consume files one by one
178        int total = exchanges.size();
179        if (total > 0) {
180            log.debug("Total {} files to consume", total);
181        }
182
183        int polledMessages = processBatch(CastUtils.cast(q));
184
185        postPollCheck(polledMessages);
186
187        return polledMessages;
188    }
189
190    public int processBatch(Queue<Object> exchanges) {
191        int total = exchanges.size();
192        int answer = total;
193
194        // limit if needed
195        if (maxMessagesPerPoll > 0 && total > maxMessagesPerPoll) {
196            log.debug("Limiting to maximum messages to poll {} as there were {} messages in this poll.", maxMessagesPerPoll, total);
197            total = maxMessagesPerPoll;
198        }
199
200        for (int index = 0; index < total && isBatchAllowed(); index++) {
201            // only loop if we are started (allowed to run)
202            // use poll to remove the head so it does not consume memory even after we have processed it
203            Exchange exchange = (Exchange) exchanges.poll();
204            // add current index and total as properties
205            exchange.setProperty(Exchange.BATCH_INDEX, index);
206            exchange.setProperty(Exchange.BATCH_SIZE, total);
207            exchange.setProperty(Exchange.BATCH_COMPLETE, index == total - 1);
208
209            // update pending number of exchanges
210            pendingExchanges = total - index - 1;
211
212            // process the current exchange
213            boolean started;
214            if (customProcessor != null) {
215                // use a custom processor
216                started = customProcessExchange(exchange, customProcessor);
217            } else {
218                // process the exchange regular
219                started = processExchange(exchange);
220            }
221
222            // if we did not start process the file then decrement the counter
223            if (!started) {
224                answer--;
225            }
226        }
227
228        // drain any in progress files as we are done with this batch
229        removeExcessiveInProgressFiles(CastUtils.cast((Deque<?>) exchanges, Exchange.class), 0);
230
231        return answer;
232    }
233
234    /**
235     * Drain any in progress files as we are done with this batch
236     *
237     * @param exchanges  the exchanges
238     * @param limit      the limit
239     */
240    protected void removeExcessiveInProgressFiles(Deque<Exchange> exchanges, int limit) {
241        // remove the file from the in progress list in case the batch was limited by max messages per poll
242        while (exchanges.size() > limit) {
243            // must remove last
244            Exchange exchange = exchanges.removeLast();
245            GenericFile<?> file = exchange.getProperty(FileComponent.FILE_EXCHANGE_FILE, GenericFile.class);
246            String key = file.getAbsoluteFilePath();
247            endpoint.getInProgressRepository().remove(key);
248        }
249    }
250
251    /**
252     * Drain any in progress files as we are done with the files
253     *
254     * @param files  the files
255     */
256    protected void removeExcessiveInProgressFiles(List<GenericFile<T>> files) {
257        for (GenericFile file : files) {
258            String key = file.getAbsoluteFilePath();
259            endpoint.getInProgressRepository().remove(key);
260        }
261    }
262
263    /**
264     * Whether or not we can continue polling for more files
265     *
266     * @param fileList  the current list of gathered files
267     * @return <tt>true</tt> to continue, <tt>false</tt> to stop due hitting maxMessagesPerPoll limit
268     */
269    public boolean canPollMoreFiles(List<?> fileList) {
270        // at this point we should not limit if we are not eager
271        if (!eagerLimitMaxMessagesPerPoll) {
272            return true;
273        }
274
275        if (maxMessagesPerPoll <= 0) {
276            // no limitation
277            return true;
278        }
279
280        // then only poll if we haven't reached the max limit
281        return fileList.size() < maxMessagesPerPoll;
282    }
283
284    /**
285     * Override if required. Perform some checks (and perhaps actions) before we poll.
286     *
287     * @return <tt>true</tt> to poll, <tt>false</tt> to skip this poll.
288     */
289    protected boolean prePollCheck() throws Exception {
290        return true;
291    }
292
293    /**
294     * Override if required. Perform some checks (and perhaps actions) after we have polled.
295     *
296     * @param polledMessages number of polled messages
297     */
298    protected void postPollCheck(int polledMessages) {
299        // noop
300    }
301
302    /**
303     * Polls the given directory for files to process
304     *
305     * @param fileName current directory or file
306     * @param fileList current list of files gathered
307     * @param depth the current depth of the directory (will start from 0)
308     * @return whether or not to continue polling, <tt>false</tt> means the maxMessagesPerPoll limit has been hit
309     */
310    protected abstract boolean pollDirectory(String fileName, List<GenericFile<T>> fileList, int depth);
311
312    /**
313     * Sets the operations to be used.
314     * <p/>
315     * Can be used to set a fresh operations in case of recovery attempts
316     *
317     * @param operations the operations
318     */
319    public void setOperations(GenericFileOperations<T> operations) {
320        this.operations = operations;
321    }
322
323    /**
324     * Whether to ignore if the file cannot be retrieved.
325     * <p/>
326     * By default an {@link GenericFileOperationFailedException} is thrown if the file cannot be retrieved.
327     * <p/>
328     * This method allows to suppress this and just ignore that.
329     *
330     * @param name        the file name
331     * @param exchange    the exchange
332     * @param cause       optional exception occurred during retrieving file
333     * @return <tt>true</tt> to ignore, <tt>false</tt> is the default.
334     */
335    protected boolean ignoreCannotRetrieveFile(String name, Exchange exchange, Exception cause) {
336        return false;
337    }
338
339    /**
340     * Processes the exchange
341     *
342     * @param exchange the exchange
343     * @return <tt>true</tt> if the file was started to be processed, <tt>false</tt> if the file was not started
344     * to be processed, for some reason (not found, or aborted etc)
345     */
346    protected boolean processExchange(final Exchange exchange) {
347        GenericFile<T> file = getExchangeFileProperty(exchange);
348        log.trace("Processing file: {}", file);
349
350        // must extract the absolute name before the begin strategy as the file could potentially be pre moved
351        // and then the file name would be changed
352        String absoluteFileName = file.getAbsoluteFilePath();
353
354        // check if we can begin processing the file
355        final GenericFileProcessStrategy<T> processStrategy = endpoint.getGenericFileProcessStrategy();
356
357        Exception beginCause = null;
358        boolean begin = false;
359        try {
360            begin = processStrategy.begin(operations, endpoint, exchange, file);
361        } catch (Exception e) {
362            beginCause = e;
363        }
364
365        if (!begin) {
366            // no something was wrong, so we need to abort and remove the file from the in progress list
367            Exception abortCause = null;
368            log.debug("{} cannot begin processing file: {}", endpoint, file);
369            try {
370                // abort
371                processStrategy.abort(operations, endpoint, exchange, file);
372            } catch (Exception e) {
373                abortCause = e;
374            } finally {
375                // begin returned false, so remove file from the in progress list as its no longer in progress
376                endpoint.getInProgressRepository().remove(absoluteFileName);
377            }
378            if (beginCause != null) {
379                String msg = endpoint + " cannot begin processing file: " + file + " due to: " + beginCause.getMessage();
380                handleException(msg, beginCause);
381            }
382            if (abortCause != null) {
383                String msg2 = endpoint + " cannot abort processing file: " + file + " due to: " + abortCause.getMessage();
384                handleException(msg2, abortCause);
385            }
386            return false;
387        }
388
389        // must use file from exchange as it can be updated due the
390        // preMoveNamePrefix/preMoveNamePostfix options
391        final GenericFile<T> target = getExchangeFileProperty(exchange);
392
393        // we can begin processing the file so update file headers on the Camel message
394        // in case it took some time to acquire read lock, and file size/timestamp has been updated since etc
395        updateFileHeaders(target, exchange.getIn());
396
397        // must use full name when downloading so we have the correct path
398        final String name = target.getAbsoluteFilePath();
399        try {
400            
401            if (isRetrieveFile()) {
402                // retrieve the file using the stream
403                log.trace("Retrieving file: {} from: {}", name, endpoint);
404    
405                // retrieve the file and check it was a success
406                boolean retrieved;
407                Exception cause = null;
408                try {
409                    retrieved = operations.retrieveFile(name, exchange);
410                } catch (Exception e) {
411                    retrieved = false;
412                    cause = e;
413                }
414
415                if (!retrieved) {
416                    if (ignoreCannotRetrieveFile(name, exchange, cause)) {
417                        log.trace("Cannot retrieve file {} maybe it does not exists. Ignoring.", name);
418                        // remove file from the in progress list as we could not retrieve it, but should ignore
419                        endpoint.getInProgressRepository().remove(absoluteFileName);
420                        return false;
421                    } else {
422                        // throw exception to handle the problem with retrieving the file
423                        // then if the method return false or throws an exception is handled the same in here
424                        // as in both cases an exception is being thrown
425                        if (cause != null && cause instanceof GenericFileOperationFailedException) {
426                            throw cause;
427                        } else {
428                            throw new GenericFileOperationFailedException("Cannot retrieve file: " + file + " from: " + endpoint, cause);
429                        }
430                    }
431                }
432    
433                log.trace("Retrieved file: {} from: {}", name, endpoint);                
434            } else {
435                log.trace("Skipped retrieval of file: {} from: {}", name, endpoint);
436                exchange.getIn().setBody(null);
437            }
438
439            // register on completion callback that does the completion strategies
440            // (for instance to move the file after we have processed it)
441            exchange.addOnCompletion(new GenericFileOnCompletion<T>(endpoint, operations, target, absoluteFileName));
442
443            log.debug("About to process file: {} using exchange: {}", target, exchange);
444
445            if (endpoint.isSynchronous()) {
446                // process synchronously
447                getProcessor().process(exchange);
448            } else {
449                // process the exchange using the async consumer to support async routing engine
450                // which can be supported by this file consumer as all the done work is
451                // provided in the GenericFileOnCompletion
452                getAsyncProcessor().process(exchange, new AsyncCallback() {
453                    public void done(boolean doneSync) {
454                        // noop
455                        if (log.isTraceEnabled()) {
456                            log.trace("Done processing file: {} {}", target, doneSync ? "synchronously" : "asynchronously");
457                        }
458                    }
459                });
460            }
461
462        } catch (Exception e) {
463            // remove file from the in progress list due to failure
464            // (cannot be in finally block due to GenericFileOnCompletion will remove it
465            // from in progress when it takes over and processes the file, which may happen
466            // by another thread at a later time. So its only safe to remove it if there was an exception)
467            endpoint.getInProgressRepository().remove(absoluteFileName);
468
469            String msg = "Error processing file " + file + " due to " + e.getMessage();
470            handleException(msg, e);
471        }
472
473        return true;
474    }
475
476    /**
477     * Updates the information on {@link Message} after we have acquired read-lock and
478     * can begin process the file.
479     *
480     * @param file    the file
481     * @param message the Camel message to update its headers
482     */
483    protected abstract void updateFileHeaders(GenericFile<T> file, Message message);
484
485    /**
486     * Override if required.  Files are retrieved / returns true by default
487     *
488     * @return <tt>true</tt> to retrieve files, <tt>false</tt> to skip retrieval of files.
489     */
490    protected boolean isRetrieveFile() {
491        return true;
492    }
493
494    /**
495     * Processes the exchange using a custom processor.
496     *
497     * @param exchange the exchange
498     * @param processor the custom processor
499     */
500    protected boolean customProcessExchange(final Exchange exchange, final Processor processor) {
501        GenericFile<T> file = getExchangeFileProperty(exchange);
502        log.trace("Custom processing file: {}", file);
503
504        // must extract the absolute name before the begin strategy as the file could potentially be pre moved
505        // and then the file name would be changed
506        String absoluteFileName = file.getAbsoluteFilePath();
507
508        try {
509            // process using the custom processor
510            processor.process(exchange);
511        } catch (Exception e) {
512            if (log.isDebugEnabled()) {
513                log.debug(endpoint + " error custom processing: " + file + " due to: " + e.getMessage() + ". This exception will be ignored.", e);
514            }
515            handleException(e);
516        } finally {
517            // always remove file from the in progress list as its no longer in progress
518            // use the original file name that was used to add it to the repository
519            // as the name can be different when using preMove option
520            endpoint.getInProgressRepository().remove(absoluteFileName);
521        }
522
523        return true;
524    }
525
526    /**
527     * Strategy for validating if the given remote file should be included or not
528     *
529     * @param file        the file
530     * @param isDirectory whether the file is a directory or a file
531     * @param files       files in the directory
532     * @return <tt>true</tt> to include the file, <tt>false</tt> to skip it
533     */
534    protected boolean isValidFile(GenericFile<T> file, boolean isDirectory, List<T> files) {
535        String absoluteFilePath = file.getAbsoluteFilePath();
536
537        if (!isMatched(file, isDirectory, files)) {
538            log.trace("File did not match. Will skip this file: {}", file);
539            return false;
540        }
541
542        // directory is always valid
543        if (isDirectory) {
544            return true;
545        }
546
547        // check if file is already in progress
548        if (endpoint.getInProgressRepository().contains(absoluteFilePath)) {
549            if (log.isTraceEnabled()) {
550                log.trace("Skipping as file is already in progress: {}", file.getFileName());
551            }
552            return false;
553        }
554
555        // if its a file then check we have the file in the idempotent registry already
556        if (endpoint.isIdempotent()) {
557            // use absolute file path as default key, but evaluate if an expression key was configured
558            String key = file.getAbsoluteFilePath();
559            if (endpoint.getIdempotentKey() != null) {
560                Exchange dummy = endpoint.createExchange(file);
561                key = endpoint.getIdempotentKey().evaluate(dummy, String.class);
562            }
563            if (key != null && endpoint.getIdempotentRepository().contains(key)) {
564                log.trace("This consumer is idempotent and the file has been consumed before matching idempotentKey: {}. Will skip this file: {}", key, file);
565                return false;
566            }
567        }
568
569        // okay so final step is to be able to add atomic as in-progress, so we are the
570        // only thread processing this file
571        return endpoint.getInProgressRepository().add(absoluteFilePath);
572    }
573
574    /**
575     * Strategy to perform file matching based on endpoint configuration.
576     * <p/>
577     * Will always return <tt>false</tt> for certain files/folders:
578     * <ul>
579     * <li>Starting with a dot</li>
580     * <li>lock files</li>
581     * </ul>
582     * And then <tt>true</tt> for directories.
583     *
584     * @param file        the file
585     * @param isDirectory whether the file is a directory or a file
586     * @param files       files in the directory
587     * @return <tt>true</tt> if the file is matched, <tt>false</tt> if not
588     */
589    protected boolean isMatched(GenericFile<T> file, boolean isDirectory, List<T> files) {
590        String name = file.getFileNameOnly();
591
592        // folders/names starting with dot is always skipped (eg. ".", ".camel", ".camelLock")
593        if (name.startsWith(".")) {
594            return false;
595        }
596
597        // lock files should be skipped
598        if (name.endsWith(FileComponent.DEFAULT_LOCK_FILE_POSTFIX)) {
599            return false;
600        }
601
602        if (endpoint.getFilter() != null) {
603            if (!endpoint.getFilter().accept(file)) {
604                return false;
605            }
606        }
607
608        if (endpoint.getAntFilter() != null) {
609            if (!endpoint.getAntFilter().accept(file)) {
610                return false;
611            }
612        }
613
614        if (isDirectory && endpoint.getFilterDirectory() != null) {
615            // create a dummy exchange as Exchange is needed for expression evaluation
616            Exchange dummy = endpoint.createExchange(file);
617            boolean matches = endpoint.getFilterDirectory().matches(dummy);
618            if (!matches) {
619                return false;
620            }
621        }
622
623        // directories are regarded as matched if filter accepted them
624        if (isDirectory) {
625            return true;
626        }
627
628        // exclude take precedence over include
629        if (excludePattern != null)  {
630            if (excludePattern.matcher(name).matches()) {
631                return false;
632            }
633        }
634        if (includePattern != null)  {
635            if (!includePattern.matcher(name).matches()) {
636                return false;
637            }
638        }
639
640        // use file expression for a simple dynamic file filter
641        if (endpoint.getFileName() != null) {
642            fileExpressionResult = evaluateFileExpression();
643            if (fileExpressionResult != null) {
644                if (!name.equals(fileExpressionResult)) {
645                    return false;
646                }
647            }
648        }
649
650        if (endpoint.getFilterFile() != null) {
651            // create a dummy exchange as Exchange is needed for expression evaluation
652            Exchange dummy = endpoint.createExchange(file);
653            boolean matches = endpoint.getFilterFile().matches(dummy);
654            if (!matches) {
655                return false;
656            }
657        }
658
659        // if done file name is enabled, then the file is only valid if a done file exists
660        if (endpoint.getDoneFileName() != null) {
661            // done file must be in same path as the file
662            String doneFileName = endpoint.createDoneFileName(file.getAbsoluteFilePath());
663            StringHelper.notEmpty(doneFileName, "doneFileName", endpoint);
664
665            // is it a done file name?
666            if (endpoint.isDoneFile(file.getFileNameOnly())) {
667                log.trace("Skipping done file: {}", file);
668                return false;
669            }
670
671            if (!isMatched(file, doneFileName, files)) {
672                return false;
673            }
674        }
675
676        return true;
677    }
678
679    /**
680     * Strategy to perform file matching based on endpoint configuration in terms of done file name.
681     *
682     * @param file         the file
683     * @param doneFileName the done file name (without any paths)
684     * @param files        files in the directory
685     * @return <tt>true</tt> if the file is matched, <tt>false</tt> if not
686     */
687    protected abstract boolean isMatched(GenericFile<T> file, String doneFileName, List<T> files);
688
689    /**
690     * Is the given file already in progress.
691     *
692     * @param file the file
693     * @return <tt>true</tt> if the file is already in progress
694     * @deprecated no longer in use, use {@link org.apache.camel.component.file.GenericFileEndpoint#getInProgressRepository()} instead.
695     */
696    @Deprecated
697    protected boolean isInProgress(GenericFile<T> file) {
698        String key = file.getAbsoluteFilePath();
699        // must use add, to have operation as atomic
700        return !endpoint.getInProgressRepository().add(key);
701    }
702
703    protected String evaluateFileExpression() {
704        if (fileExpressionResult == null && endpoint.getFileName() != null) {
705            // create a dummy exchange as Exchange is needed for expression evaluation
706            Exchange dummy = endpoint.createExchange();
707            fileExpressionResult = endpoint.getFileName().evaluate(dummy, String.class);
708            if (dummy.getException() != null) {
709                throw ObjectHelper.wrapRuntimeCamelException(dummy.getException());
710            }
711        }
712        return fileExpressionResult;
713    }
714
715    @SuppressWarnings("unchecked")
716    private GenericFile<T> getExchangeFileProperty(Exchange exchange) {
717        return (GenericFile<T>) exchange.getProperty(FileComponent.FILE_EXCHANGE_FILE);
718    }
719
720    @Override
721    protected void doStart() throws Exception {
722        super.doStart();
723    }
724
725    @Override
726    protected void doStop() throws Exception {
727        prepareOnStartup = false;
728        super.doStop();
729    }
730}