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: 732209 $
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 }