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.HashMap;
023    import java.util.Map;
024    
025    import org.apache.camel.Consumer;
026    import org.apache.camel.ExchangePattern;
027    import org.apache.camel.Expression;
028    import org.apache.camel.Message;
029    import org.apache.camel.Processor;
030    import org.apache.camel.Producer;
031    import org.apache.camel.impl.ScheduledPollEndpoint;
032    import org.apache.camel.language.simple.FileLanguage;
033    import org.apache.camel.util.FactoryFinder;
034    import org.apache.camel.util.ObjectHelper;
035    import org.apache.camel.util.UuidGenerator;
036    import org.apache.commons.logging.Log;
037    import org.apache.commons.logging.LogFactory;
038    
039    /**
040     * A <a href="http://activemq.apache.org/camel/file.html">File Endpoint</a> for
041     * working with file systems
042     *
043     * @version $Revision: 65775 $
044     */
045    public class FileEndpoint extends ScheduledPollEndpoint<FileExchange> {
046        public static final transient String DEFAULT_LOCK_FILE_POSTFIX = ".camelLock";
047    
048        private static final transient Log LOG = LogFactory.getLog(FileEndpoint.class);
049        private static final transient String DEFAULT_STRATEGYFACTORY_CLASS =
050            "org.apache.camel.component.file.strategy.FileProcessStrategyFactory";
051    
052        private File file;
053        private FileProcessStrategy fileProcessStrategy;
054        private boolean autoCreate = true;
055        private boolean lock = true;
056        private boolean delete;
057        private boolean noop;
058        private boolean append = true;
059        private String moveNamePrefix;
060        private String moveNamePostfix;
061        private String[] excludedNamePrefixes;
062        private String[] excludedNamePostfixes;
063        private String preMoveNamePrefix;
064        private String preMoveNamePostfix;
065        private String excludedNamePrefix;
066        private String excludedNamePostfix;
067        private int bufferSize = 128 * 1024;
068        private boolean ignoreFileNameHeader;
069        private Expression expression;
070        private Expression preMoveExpression;
071    
072        protected FileEndpoint(File file, String endpointUri, FileComponent component) {
073            super(endpointUri, component);
074            this.file = file;
075        }
076    
077        public FileEndpoint(String endpointUri, File file) {
078            super(endpointUri);
079            this.file = file;
080        }
081    
082        public FileEndpoint(File file) {
083            this.file = file;
084        }
085    
086        public FileEndpoint() {
087        }
088    
089        public Producer<FileExchange> createProducer() throws Exception {
090            Producer<FileExchange> result = new FileProducer(this);
091            return result;
092        }
093    
094        public Consumer<FileExchange> createConsumer(Processor processor) throws Exception {
095            Consumer<FileExchange> result = new FileConsumer(this, processor);
096    
097            if (isDelete() && (getMoveNamePrefix() != null || getMoveNamePostfix() != null || getExpression() != null)) {
098                throw new IllegalArgumentException("You cannot set delet and a moveNamePrefix, moveNamePostfix or expression option");
099            }
100            
101            configureConsumer(result);
102            return result;
103        }
104    
105        /**
106         * Create a new exchange for communicating with this endpoint
107         *
108         * @param file  the file
109         * @return the created exchange
110         */
111        public FileExchange createExchange(File file) {
112            return new FileExchange(getCamelContext(), getExchangePattern(), file);
113        }
114    
115        @Override
116        public FileExchange createExchange() {
117            return createExchange(getFile());
118        }
119    
120        @Override
121        public FileExchange createExchange(ExchangePattern pattern) {
122            return new FileExchange(getCamelContext(), pattern, file);
123        }
124    
125        /**
126         * Return the file name that will be auto-generated for the given message if none is provided
127         */
128        public String getGeneratedFileName(Message message) {
129            return getFileFriendlyMessageId(message.getMessageId());
130        }
131    
132        /**
133         * Configures the given message with the file which sets the body to the file object
134         * and sets the {@link FileComponent#HEADER_FILE_NAME} header.
135         */
136        public void configureMessage(File file, Message message) {
137            message.setBody(file);
138            String relativePath = file.getPath().substring(getFile().getPath().length());
139            if (relativePath.startsWith(File.separator) || relativePath.startsWith("/")) {
140                relativePath = relativePath.substring(1);
141            }
142            message.setHeader(FileComponent.HEADER_FILE_NAME, relativePath);
143        }
144    
145        public File getFile() {
146            ObjectHelper.notNull(file, "file");
147            if (autoCreate && !file.exists()) {
148                file.mkdirs();
149            }
150            return file;
151        }
152    
153        public void setFile(File file) {
154            this.file = file;
155        }
156    
157        public boolean isSingleton() {
158            return true;
159        }
160    
161        public boolean isAutoCreate() {
162            return this.autoCreate;
163        }
164    
165        public void setAutoCreate(boolean autoCreate) {
166            this.autoCreate = autoCreate;
167        }
168    
169        public FileProcessStrategy getFileStrategy() {
170            if (fileProcessStrategy == null) {
171                fileProcessStrategy = createFileStrategy();
172                LOG.debug("Using file process strategy: " + fileProcessStrategy);
173            }
174            return fileProcessStrategy;
175        }
176    
177        /**
178         * Sets the strategy to be used when the file has been processed such as
179         * deleting or renaming it etc.
180         *
181         * @param fileProcessStrategy the new strategy to use
182         */
183        public void setFileStrategy(FileProcessStrategy fileProcessStrategy) {
184            this.fileProcessStrategy = fileProcessStrategy;
185        }
186    
187        public boolean isDelete() {
188            return delete;
189        }
190    
191        public void setDelete(boolean delete) {
192            this.delete = delete;
193        }
194    
195        public boolean isLock() {
196            return lock;
197        }
198    
199        public void setLock(boolean lock) {
200            this.lock = lock;
201        }
202    
203        public String getMoveNamePostfix() {
204            return moveNamePostfix;
205        }
206    
207        /**
208         * Sets the name postfix appended to moved files. For example to rename all
209         * the files from <tt>*</tt> to <tt>*.done</tt> set this value to <tt>.done</tt>
210         */
211        public void setMoveNamePostfix(String moveNamePostfix) {
212            this.moveNamePostfix = moveNamePostfix;
213        }
214    
215        public String getMoveNamePrefix() {
216            return moveNamePrefix;
217        }
218    
219        /**
220         * Sets the name prefix appended to moved files. For example to move
221         * processed files into a hidden directory called <tt>.camel</tt> set this value to
222         * <tt>.camel/</tt>
223         */
224        public void setMoveNamePrefix(String moveNamePrefix) {
225            this.moveNamePrefix = moveNamePrefix;
226        }
227    
228        public String[] getExcludedNamePrefixes() {
229            return excludedNamePrefixes;
230        }
231    
232        /**
233         * Sets the excluded file name prefixes, such as <tt>"."</tt> for hidden files which
234         * are excluded by default
235         *
236         * @deprecated use ExcludedNamePrefix. Will be removed in Camel 2.0.
237         */
238        public void setExcludedNamePrefixes(String[] excludedNamePrefixes) {
239            this.excludedNamePrefixes = excludedNamePrefixes;
240        }
241    
242        public String[] getExcludedNamePostfixes() {
243            return excludedNamePostfixes;
244        }
245    
246        /**
247         * Sets the excluded file name postfixes, such as {@link FileEndpoint#DEFAULT_LOCK_FILE_POSTFIX}
248         * to ignore lock files by default.
249         *
250         * @deprecated use ExcludedNamePostfix. Will be removed in Camel 2.0.
251         */
252        public void setExcludedNamePostfixes(String[] excludedNamePostfixes) {
253            this.excludedNamePostfixes = excludedNamePostfixes;
254        }
255    
256        public String getPreMoveNamePrefix() {
257            return preMoveNamePrefix;
258        }
259    
260        public void setPreMoveNamePrefix(String preMoveNamePrefix) {
261            this.preMoveNamePrefix = preMoveNamePrefix;
262        }
263    
264        /**
265         * Sets the name prefix appended to pre moved files. For example to move
266         * files before processing into a inprogress directory called <tt>.inprogress</tt> set this value to
267         * <tt>.inprogress/</tt>
268         */
269        public String getPreMoveNamePostfix() {
270            return preMoveNamePostfix;
271        }
272    
273        /**
274         * Sets the name postfix appended to pre moved files. For example to rename
275         * files before processing from <tt>*</tt> to <tt>*.inprogress</tt> set this value to <tt>.inprogress</tt>
276         */
277        public void setPreMoveNamePostfix(String preMoveNamePostfix) {
278            this.preMoveNamePostfix = preMoveNamePostfix;
279        }
280    
281        public boolean isNoop() {
282            return noop;
283        }
284    
285        /**
286         * If set to true then the default {@link FileProcessStrategy} will be to use the
287         * {@link org.apache.camel.component.file.strategy.NoOpFileProcessStrategy NoOpFileProcessStrategy}
288         * to not move or copy processed files
289         */
290        public void setNoop(boolean noop) {
291            this.noop = noop;
292        }
293    
294        public boolean isAppend() {
295            return append;
296        }
297    
298        /**
299         * When writing do we append to the end of the file, or replace it?
300         * The default is to append
301         */
302        public void setAppend(boolean append) {
303            this.append = append;
304        }
305    
306        public int getBufferSize() {
307            return bufferSize;
308        }
309    
310        /**
311         * Sets the buffer size used to read/write files
312         */
313        public void setBufferSize(int bufferSize) {
314            this.bufferSize = bufferSize;
315        }
316    
317        public boolean isIgnoreFileNameHeader() {
318            return ignoreFileNameHeader;
319        }
320    
321        /**
322         * If this flag is enabled then producers will ignore the {@link FileComponent#HEADER_FILE_NAME}
323         * header and generate a new dynamic file
324         */
325        public void setIgnoreFileNameHeader(boolean ignoreFileNameHeader) {
326            this.ignoreFileNameHeader = ignoreFileNameHeader;
327        }
328    
329        public String getExcludedNamePrefix() {
330            return excludedNamePrefix;
331        }
332    
333        public void setExcludedNamePrefix(String excludedNamePrefix) {
334            this.excludedNamePrefix = excludedNamePrefix;
335        }
336    
337        public String getExcludedNamePostfix() {
338            return excludedNamePostfix;
339        }
340    
341        public void setExcludedNamePostfix(String excludedNamePostfix) {
342            this.excludedNamePostfix = excludedNamePostfix;
343        }
344    
345        public Expression getExpression() {
346            return expression;
347        }
348    
349        public void setExpression(Expression expression) {
350            this.expression = expression;
351        }
352    
353        /**
354         * Sets the expression based on {@link FileLanguage}
355         */
356        public void setExpression(String fileLanguageExpression) {
357            this.expression = FileLanguage.file(fileLanguageExpression);
358        }
359    
360        public Expression getPreMoveExpression() {
361            return preMoveExpression;
362        }
363    
364        public void setPreMoveExpression(Expression expression) {
365            this.preMoveExpression = expression;
366        }
367    
368        /**
369         * Sets the pre move expression based on {@link FileLanguage}
370         */
371        public void setPreMoveExpression(String fileLanguageExpression) {
372            this.preMoveExpression = FileLanguage.file(fileLanguageExpression);
373        }
374    
375        /**
376         * A strategy method to lazily create the file strategy
377         */
378        protected FileProcessStrategy createFileStrategy() {
379            Class<?> factory = null;
380            try {
381                FactoryFinder finder = getCamelContext().createFactoryFinder("META-INF/services/org/apache/camel/component/");
382                factory = finder.findClass("file", "strategy.factory.");
383            } catch (ClassNotFoundException e) {
384                LOG.debug("'strategy.factory.class' not found", e);
385            } catch (IOException e) {
386                LOG.debug("No strategy factory defined in 'META-INF/services/org/apache/camel/component/file'", e);
387            }
388    
389            if (factory == null) {
390                // use default
391                factory = ObjectHelper.loadClass(DEFAULT_STRATEGYFACTORY_CLASS);
392                if (factory == null) {
393                    throw new TypeNotPresentException("FileProcessStrategyFactory class not found", null);
394                }
395            }
396    
397            try {
398                Method factoryMethod = factory.getMethod("createFileProcessStrategy", Map.class);
399                return (FileProcessStrategy) ObjectHelper.invokeMethod(factoryMethod, null, getParamsAsMap());
400            } catch (NoSuchMethodException e) {
401                throw new TypeNotPresentException(factory.getSimpleName()
402                    + ".createFileProcessStrategy(Properties params) method not found", e);
403            }
404        }
405    
406        protected Map<String, Object> getParamsAsMap() {
407            Map<String, Object> params = new HashMap<String, Object>();
408    
409            if (isNoop()) {
410                params.put("noop", Boolean.toString(true));
411            }
412            if (isDelete()) {
413                params.put("delete", Boolean.toString(true));
414            }
415            if (isAppend()) {
416                params.put("append", Boolean.toString(true));
417            }
418            if (isLock()) {
419                params.put("lock", Boolean.toString(true));
420            }
421            if (moveNamePrefix != null) {
422                params.put("moveNamePrefix", moveNamePrefix);
423            }
424            if (moveNamePostfix != null) {
425                params.put("moveNamePostfix", moveNamePostfix);
426            }
427            if (preMoveNamePrefix != null) {
428                params.put("preMoveNamePrefix", preMoveNamePrefix);
429            }
430            if (preMoveNamePostfix != null) {
431                params.put("preMoveNamePostfix", preMoveNamePostfix);
432            }
433            if (expression != null) {
434                params.put("expression", expression);
435            }
436            if (preMoveExpression != null) {
437                params.put("preMoveExpression", preMoveExpression);
438            }
439    
440            return params;
441        }
442    
443        @Override
444        protected String createEndpointUri() {
445            return "file://" + getFile().getAbsolutePath();
446        }
447    
448        protected String getFileFriendlyMessageId(String id) {
449            return UuidGenerator.generateSanitizedId(id);
450        }
451    }