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