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.File;
020    import java.io.IOException;
021    import java.lang.reflect.Method;
022    import java.util.Comparator;
023    import java.util.HashMap;
024    import java.util.Map;
025    
026    import org.apache.camel.CamelContext;
027    import org.apache.camel.Component;
028    import org.apache.camel.Exchange;
029    import org.apache.camel.Expression;
030    import org.apache.camel.Message;
031    import org.apache.camel.Processor;
032    import org.apache.camel.impl.ScheduledPollEndpoint;
033    import org.apache.camel.processor.idempotent.MemoryIdempotentRepository;
034    import org.apache.camel.spi.FactoryFinder;
035    import org.apache.camel.spi.IdempotentRepository;
036    import org.apache.camel.spi.Language;
037    import org.apache.camel.util.FileUtil;
038    import org.apache.camel.util.ObjectHelper;
039    import org.apache.camel.util.StringHelper;
040    import org.apache.commons.logging.Log;
041    import org.apache.commons.logging.LogFactory;
042    
043    /**
044     * Generic FileEndpoint
045     */
046    public abstract class GenericFileEndpoint<T> extends ScheduledPollEndpoint {
047    
048        protected static final transient String DEFAULT_STRATEGYFACTORY_CLASS = "org.apache.camel.component.file.strategy.GenericFileProcessStrategyFactory";
049        protected static final transient int DEFAULT_IDEMPOTENT_CACHE_SIZE = 1000;
050    
051        protected final transient Log log = LogFactory.getLog(getClass());
052    
053        protected GenericFileProcessStrategy<T> processStrategy;
054        protected GenericFileConfiguration configuration;
055    
056        protected IdempotentRepository<String> inProgressRepository = new MemoryIdempotentRepository();
057        protected String localWorkDirectory;
058        protected boolean autoCreate = true;
059        protected int bufferSize = 128 * 1024;
060        protected GenericFileExist fileExist = GenericFileExist.Override;
061        protected boolean noop;
062        protected boolean recursive;
063        protected boolean delete;
064        protected boolean flatten;
065        protected int maxMessagesPerPoll;
066        protected String tempPrefix;
067        protected Expression tempFileName;
068        protected String include;
069        protected String exclude;
070        protected Expression fileName;
071        protected Expression move;
072        protected Expression moveFailed;
073        protected Expression preMove;
074        protected Boolean idempotent;
075        protected IdempotentRepository<String> idempotentRepository;
076        protected GenericFileFilter<T> filter;
077        protected Comparator<GenericFile<T>> sorter;
078        protected Comparator<Exchange> sortBy;
079        protected String readLock = "none";
080        protected long readLockTimeout = 10000;
081        protected GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy;
082        protected boolean keepLastModified;
083    
084        public GenericFileEndpoint() {
085        }
086    
087        public GenericFileEndpoint(String endpointUri, Component component) {
088            super(endpointUri, component);
089        }
090    
091        public boolean isSingleton() {
092            return true;
093        }
094    
095        public abstract GenericFileConsumer<T> createConsumer(Processor processor) throws Exception;
096    
097        public abstract GenericFileProducer<T> createProducer() throws Exception;
098    
099        public abstract Exchange createExchange(GenericFile<T> file);
100    
101        public abstract String getScheme();
102        
103        public abstract char getFileSeparator();
104        
105        public abstract boolean isAbsolute(String name);
106        
107        /**
108         * Return the file name that will be auto-generated for the given message if
109         * none is provided
110         */
111        public String getGeneratedFileName(Message message) {
112            return StringHelper.sanitize(message.getMessageId());
113        }
114    
115        public GenericFileProcessStrategy<T> getGenericFileProcessStrategy() {
116            if (processStrategy == null) {
117                processStrategy = createGenericFileStrategy();
118                if (log.isDebugEnabled()) {
119                    log.debug("Using Generic file process strategy: " + processStrategy);
120                }
121            }
122            return processStrategy;
123        }
124    
125        /**
126         * A strategy method to lazily create the file strategy
127         */
128        @SuppressWarnings("unchecked")
129        protected GenericFileProcessStrategy<T> createGenericFileStrategy() {
130            Class<?> factory = null;
131            try {
132                FactoryFinder finder = getCamelContext().getFactoryFinder("META-INF/services/org/apache/camel/component/");
133                factory = finder.findClass(getScheme(), "strategy.factory.");
134            } catch (ClassNotFoundException e) {
135                log.debug("'strategy.factory.class' not found", e);
136            } catch (IOException e) {
137                log.debug("No strategy factory defined in 'META-INF/services/org/apache/camel/component/'", e);
138            }
139    
140            if (factory == null) {
141                // use default
142                factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS);
143                if (factory == null) {
144                    throw new TypeNotPresentException(DEFAULT_STRATEGYFACTORY_CLASS + " class not found", null);
145                }
146            }
147    
148            try {
149                Method factoryMethod = factory.getMethod("createGenericFileProcessStrategy", CamelContext.class, Map.class);
150                return (GenericFileProcessStrategy<T>) ObjectHelper.invokeMethod(factoryMethod, null, getCamelContext(), getParamsAsMap());
151            } catch (NoSuchMethodException e) {
152                throw new TypeNotPresentException(factory.getSimpleName() + ".createGenericFileProcessStrategy method not found", e);
153            }
154        }
155    
156        public boolean isNoop() {
157            return noop;
158        }
159    
160        public void setNoop(boolean noop) {
161            this.noop = noop;
162        }
163    
164        public boolean isRecursive() {
165            return recursive;
166        }
167    
168        public void setRecursive(boolean recursive) {
169            this.recursive = recursive;
170        }
171    
172        public String getInclude() {
173            return include;
174        }
175    
176        public void setInclude(String include) {
177            this.include = include;
178        }
179    
180        public String getExclude() {
181            return exclude;
182        }
183    
184        public void setExclude(String exclude) {
185            this.exclude = exclude;
186        }
187    
188        public boolean isDelete() {
189            return delete;
190        }
191    
192        public void setDelete(boolean delete) {
193            this.delete = delete;
194        }
195    
196        public boolean isFlatten() {
197            return flatten;
198        }
199    
200        public void setFlatten(boolean flatten) {
201            this.flatten = flatten;
202        }
203    
204        public Expression getMove() {
205            return move;
206        }
207    
208        public void setMove(Expression move) {
209            this.move = move;
210        }
211    
212        /**
213         * Sets the move failure expression based on
214         * {@link org.apache.camel.language.simple.FileLanguage}
215         */
216        public void setMoveFailed(String fileLanguageExpression) {
217            String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
218            this.moveFailed = createFileLanguageExpression(expression);
219        }
220    
221        public Expression getMoveFailed() {
222            return moveFailed;
223        }
224    
225        public void setMoveFailed(Expression moveFailed) {
226            this.moveFailed = moveFailed;
227        }
228    
229        /**
230         * Sets the move expression based on
231         * {@link org.apache.camel.language.simple.FileLanguage}
232         */
233        public void setMove(String fileLanguageExpression) {
234            String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
235            this.move = createFileLanguageExpression(expression);
236        }
237    
238        public Expression getPreMove() {
239            return preMove;
240        }
241    
242        public void setPreMove(Expression preMove) {
243            this.preMove = preMove;
244        }
245    
246        /**
247         * Sets the pre move expression based on
248         * {@link org.apache.camel.language.simple.FileLanguage}
249         */
250        public void setPreMove(String fileLanguageExpression) {
251            String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
252            this.preMove = createFileLanguageExpression(expression);
253        }
254    
255        public Expression getFileName() {
256            return fileName;
257        }
258    
259        public void setFileName(Expression fileName) {
260            this.fileName = fileName;
261        }
262    
263        /**
264         * Sets the file expression based on
265         * {@link org.apache.camel.language.simple.FileLanguage}
266         */
267        public void setFileName(String fileLanguageExpression) {
268            this.fileName = createFileLanguageExpression(fileLanguageExpression);
269        }
270    
271        public Boolean isIdempotent() {
272            return idempotent != null ? idempotent : false;
273        }
274    
275        boolean isIdempotentSet() {
276            return idempotent != null;
277        }
278    
279        public void setIdempotent(Boolean idempotent) {
280            this.idempotent = idempotent;
281        }
282    
283        public IdempotentRepository<String> getIdempotentRepository() {
284            return idempotentRepository;
285        }
286    
287        public void setIdempotentRepository(IdempotentRepository<String> idempotentRepository) {
288            this.idempotentRepository = idempotentRepository;
289        }
290    
291        public GenericFileFilter<T> getFilter() {
292            return filter;
293        }
294    
295        public void setFilter(GenericFileFilter<T> filter) {
296            this.filter = filter;
297        }
298    
299        public Comparator<GenericFile<T>> getSorter() {
300            return sorter;
301        }
302    
303        public void setSorter(Comparator<GenericFile<T>> sorter) {
304            this.sorter = sorter;
305        }
306    
307        public Comparator<Exchange> getSortBy() {
308            return sortBy;
309        }
310    
311        public void setSortBy(Comparator<Exchange> sortBy) {
312            this.sortBy = sortBy;
313        }
314    
315        public void setSortBy(String expression) {
316            setSortBy(expression, false);
317        }
318    
319        public void setSortBy(String expression, boolean reverse) {
320            setSortBy(GenericFileDefaultSorter.sortByFileLanguage(getCamelContext(), expression, reverse));
321        }
322    
323        public String getTempPrefix() {
324            return tempPrefix;
325        }
326    
327        /**
328         * Enables and uses temporary prefix when writing files, after write it will
329         * be renamed to the correct name.
330         */
331        public void setTempPrefix(String tempPrefix) {
332            this.tempPrefix = tempPrefix;
333            // use only name as we set a prefix in from on the name
334            setTempFileName(tempPrefix + "${file:onlyname}");
335        }
336    
337        public Expression getTempFileName() {
338            return tempFileName;
339        }
340    
341        public void setTempFileName(Expression tempFileName) {
342            this.tempFileName = tempFileName;
343        }
344    
345        public void setTempFileName(String tempFileNameExpression) {
346            this.tempFileName = createFileLanguageExpression(tempFileNameExpression);
347        }
348    
349        public GenericFileConfiguration getConfiguration() {
350            if (configuration == null) {
351                configuration = new GenericFileConfiguration();
352            }
353            return configuration;
354        }
355    
356        public void setConfiguration(GenericFileConfiguration configuration) {
357            this.configuration = configuration;
358        }
359    
360        public GenericFileExclusiveReadLockStrategy<T> getExclusiveReadLockStrategy() {
361            return exclusiveReadLockStrategy;
362        }
363    
364        public void setExclusiveReadLockStrategy(GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy) {
365            this.exclusiveReadLockStrategy = exclusiveReadLockStrategy;
366        }
367    
368        public String getReadLock() {
369            return readLock;
370        }
371    
372        public void setReadLock(String readLock) {
373            this.readLock = readLock;
374        }
375    
376        public long getReadLockTimeout() {
377            return readLockTimeout;
378        }
379    
380        public void setReadLockTimeout(long readLockTimeout) {
381            this.readLockTimeout = readLockTimeout;
382        }
383    
384        public int getBufferSize() {
385            return bufferSize;
386        }
387    
388        public void setBufferSize(int bufferSize) {
389            if (bufferSize <= 0) {
390                throw new IllegalArgumentException("BufferSize must be a positive value, was " + bufferSize);
391            }
392            this.bufferSize = bufferSize;
393        }
394    
395        public GenericFileExist getFileExist() {
396            return fileExist;
397        }
398    
399        public void setFileExist(GenericFileExist fileExist) {
400            this.fileExist = fileExist;
401        }
402    
403        public boolean isAutoCreate() {
404            return autoCreate;
405        }
406    
407        public void setAutoCreate(boolean autoCreate) {
408            this.autoCreate = autoCreate;
409        }
410    
411        public GenericFileProcessStrategy<T> getProcessStrategy() {
412            return processStrategy;
413        }
414    
415        public void setProcessStrategy(GenericFileProcessStrategy<T> processStrategy) {
416            this.processStrategy = processStrategy;
417        }
418    
419        public String getLocalWorkDirectory() {
420            return localWorkDirectory;
421        }
422    
423        public void setLocalWorkDirectory(String localWorkDirectory) {
424            this.localWorkDirectory = localWorkDirectory;
425        }
426    
427        public int getMaxMessagesPerPoll() {
428            return maxMessagesPerPoll;
429        }
430    
431        public void setMaxMessagesPerPoll(int maxMessagesPerPoll) {
432            this.maxMessagesPerPoll = maxMessagesPerPoll;
433        }
434    
435        public IdempotentRepository<String> getInProgressRepository() {
436            return inProgressRepository;
437        }
438    
439        public void setInProgressRepository(IdempotentRepository<String> inProgressRepository) {
440            this.inProgressRepository = inProgressRepository;
441        }
442    
443        public boolean isKeepLastModified() {
444            return keepLastModified;
445        }
446    
447        public void setKeepLastModified(boolean keepLastModified) {
448            this.keepLastModified = keepLastModified;
449        }
450    
451        /**
452         * Configures the given message with the file which sets the body to the
453         * file object.
454         */
455        public void configureMessage(GenericFile<T> file, Message message) {
456            message.setBody(file);
457    
458            if (flatten) {
459                // when flatten the file name should not contain any paths
460                message.setHeader(Exchange.FILE_NAME, file.getFileNameOnly());
461            } else {
462                // compute name to set on header that should be relative to starting directory
463                String name = file.isAbsolute() ? file.getAbsoluteFilePath() : file.getRelativeFilePath();
464    
465                // skip leading endpoint configured directory
466                String endpointPath = getConfiguration().getDirectory();
467                if (ObjectHelper.isNotEmpty(endpointPath) && name.startsWith(endpointPath)) {
468                    name = ObjectHelper.after(name, getConfiguration().getDirectory() + File.separator);
469                }
470    
471                // adjust filename
472                message.setHeader(Exchange.FILE_NAME, name);
473            }
474        }
475    
476        /**
477         * Strategy to configure the move or premove option based on a String input.
478         * <p/>
479         * @param expression the original string input
480         * @return configured string or the original if no modifications is needed
481         */
482        protected String configureMoveOrPreMoveExpression(String expression) {
483            // if the expression already have ${ } placeholders then pass it unmodified
484            if (expression.indexOf("${") != -1) {
485                return expression;
486            }
487    
488            // remove trailing slash
489            expression = FileUtil.stripTrailingSeparator(expression);
490    
491            StringBuilder sb = new StringBuilder();
492    
493            // if relative then insert start with the parent folder
494            if (!isAbsolute(expression)) {
495                sb.append("${file:parent}");
496                sb.append(getFileSeparator());
497            }
498            // insert the directory the end user provided
499            sb.append(expression);
500            // append only the filename (file:name can contain a relative path, so we must use onlyname)
501            sb.append(getFileSeparator());
502            sb.append("${file:onlyname}");
503    
504            return sb.toString();
505        }
506    
507        protected Map<String, Object> getParamsAsMap() {
508            Map<String, Object> params = new HashMap<String, Object>();
509    
510            if (isNoop()) {
511                params.put("noop", Boolean.toString(true));
512            }
513            if (isDelete()) {
514                params.put("delete", Boolean.toString(true));
515            }
516            if (move != null) {
517                params.put("move", move);
518            }
519            if (moveFailed != null) {
520                params.put("moveFailed", moveFailed);
521            }
522            if (preMove != null) {
523                params.put("preMove", preMove);
524            }
525            if (exclusiveReadLockStrategy != null) {
526                params.put("exclusiveReadLockStrategy", exclusiveReadLockStrategy);
527            }
528            if (readLock != null) {
529                params.put("readLock", readLock);
530            }
531            if (readLockTimeout > 0) {
532                params.put("readLockTimeout", readLockTimeout);
533            }
534    
535            return params;
536        }
537    
538        private Expression createFileLanguageExpression(String expression) {
539            Language language;
540            // only use file language if the name is complex (eg. using $)
541            if (expression.contains("$")) {
542                language = getCamelContext().resolveLanguage("file");
543            } else {
544                language = getCamelContext().resolveLanguage("constant");
545            }
546            return language.createExpression(expression);
547        }
548    }