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