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