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 }