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.Comparator;
023 import java.util.HashMap;
024 import java.util.Map;
025
026 import org.apache.camel.CamelContext;
027 import org.apache.camel.Component;
028 import org.apache.camel.Exchange;
029 import org.apache.camel.Expression;
030 import org.apache.camel.Message;
031 import org.apache.camel.Processor;
032 import org.apache.camel.impl.ScheduledPollEndpoint;
033 import org.apache.camel.processor.idempotent.MemoryIdempotentRepository;
034 import org.apache.camel.spi.FactoryFinder;
035 import org.apache.camel.spi.IdempotentRepository;
036 import org.apache.camel.spi.Language;
037 import org.apache.camel.util.FileUtil;
038 import org.apache.camel.util.ObjectHelper;
039 import org.apache.camel.util.StringHelper;
040 import org.apache.commons.logging.Log;
041 import org.apache.commons.logging.LogFactory;
042
043 /**
044 * Generic FileEndpoint
045 */
046 public abstract class GenericFileEndpoint<T> extends ScheduledPollEndpoint {
047
048 protected static final transient String DEFAULT_STRATEGYFACTORY_CLASS = "org.apache.camel.component.file.strategy.GenericFileProcessStrategyFactory";
049 protected static final transient int DEFAULT_IDEMPOTENT_CACHE_SIZE = 1000;
050
051 protected final transient Log log = LogFactory.getLog(getClass());
052
053 protected GenericFileProcessStrategy<T> processStrategy;
054 protected GenericFileConfiguration configuration;
055
056 protected IdempotentRepository<String> inProgressRepository = new MemoryIdempotentRepository();
057 protected String localWorkDirectory;
058 protected boolean autoCreate = true;
059 protected int bufferSize = 128 * 1024;
060 protected GenericFileExist fileExist = GenericFileExist.Override;
061 protected boolean noop;
062 protected boolean recursive;
063 protected boolean delete;
064 protected boolean flatten;
065 protected int maxMessagesPerPoll;
066 protected String tempPrefix;
067 protected Expression tempFileName;
068 protected String include;
069 protected String exclude;
070 protected Expression fileName;
071 protected Expression move;
072 protected Expression moveFailed;
073 protected Expression preMove;
074 protected Boolean idempotent;
075 protected IdempotentRepository<String> idempotentRepository;
076 protected GenericFileFilter<T> filter;
077 protected Comparator<GenericFile<T>> sorter;
078 protected Comparator<Exchange> sortBy;
079 protected String readLock = "none";
080 protected long readLockTimeout = 10000;
081 protected GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy;
082 protected boolean keepLastModified;
083
084 public GenericFileEndpoint() {
085 }
086
087 public GenericFileEndpoint(String endpointUri, Component component) {
088 super(endpointUri, component);
089 }
090
091 public boolean isSingleton() {
092 return true;
093 }
094
095 public abstract GenericFileConsumer<T> createConsumer(Processor processor) throws Exception;
096
097 public abstract GenericFileProducer<T> createProducer() throws Exception;
098
099 public abstract Exchange createExchange(GenericFile<T> file);
100
101 public abstract String getScheme();
102
103 public abstract char getFileSeparator();
104
105 public abstract boolean isAbsolute(String name);
106
107 /**
108 * Return the file name that will be auto-generated for the given message if
109 * none is provided
110 */
111 public String getGeneratedFileName(Message message) {
112 return StringHelper.sanitize(message.getMessageId());
113 }
114
115 public GenericFileProcessStrategy<T> getGenericFileProcessStrategy() {
116 if (processStrategy == null) {
117 processStrategy = createGenericFileStrategy();
118 if (log.isDebugEnabled()) {
119 log.debug("Using Generic file process strategy: " + processStrategy);
120 }
121 }
122 return processStrategy;
123 }
124
125 /**
126 * A strategy method to lazily create the file strategy
127 */
128 @SuppressWarnings("unchecked")
129 protected GenericFileProcessStrategy<T> createGenericFileStrategy() {
130 Class<?> factory = null;
131 try {
132 FactoryFinder finder = getCamelContext().getFactoryFinder("META-INF/services/org/apache/camel/component/");
133 factory = finder.findClass(getScheme(), "strategy.factory.");
134 } catch (ClassNotFoundException e) {
135 log.debug("'strategy.factory.class' not found", e);
136 } catch (IOException e) {
137 log.debug("No strategy factory defined in 'META-INF/services/org/apache/camel/component/'", e);
138 }
139
140 if (factory == null) {
141 // use default
142 factory = this.getCamelContext().getClassResolver().resolveClass(DEFAULT_STRATEGYFACTORY_CLASS);
143 if (factory == null) {
144 throw new TypeNotPresentException(DEFAULT_STRATEGYFACTORY_CLASS + " class not found", null);
145 }
146 }
147
148 try {
149 Method factoryMethod = factory.getMethod("createGenericFileProcessStrategy", CamelContext.class, Map.class);
150 return (GenericFileProcessStrategy<T>) ObjectHelper.invokeMethod(factoryMethod, null, getCamelContext(), getParamsAsMap());
151 } catch (NoSuchMethodException e) {
152 throw new TypeNotPresentException(factory.getSimpleName() + ".createGenericFileProcessStrategy method not found", e);
153 }
154 }
155
156 public boolean isNoop() {
157 return noop;
158 }
159
160 public void setNoop(boolean noop) {
161 this.noop = noop;
162 }
163
164 public boolean isRecursive() {
165 return recursive;
166 }
167
168 public void setRecursive(boolean recursive) {
169 this.recursive = recursive;
170 }
171
172 public String getInclude() {
173 return include;
174 }
175
176 public void setInclude(String include) {
177 this.include = include;
178 }
179
180 public String getExclude() {
181 return exclude;
182 }
183
184 public void setExclude(String exclude) {
185 this.exclude = exclude;
186 }
187
188 public boolean isDelete() {
189 return delete;
190 }
191
192 public void setDelete(boolean delete) {
193 this.delete = delete;
194 }
195
196 public boolean isFlatten() {
197 return flatten;
198 }
199
200 public void setFlatten(boolean flatten) {
201 this.flatten = flatten;
202 }
203
204 public Expression getMove() {
205 return move;
206 }
207
208 public void setMove(Expression move) {
209 this.move = move;
210 }
211
212 /**
213 * Sets the move failure expression based on
214 * {@link org.apache.camel.language.simple.FileLanguage}
215 */
216 public void setMoveFailed(String fileLanguageExpression) {
217 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
218 this.moveFailed = createFileLanguageExpression(expression);
219 }
220
221 public Expression getMoveFailed() {
222 return moveFailed;
223 }
224
225 public void setMoveFailed(Expression moveFailed) {
226 this.moveFailed = moveFailed;
227 }
228
229 /**
230 * Sets the move expression based on
231 * {@link org.apache.camel.language.simple.FileLanguage}
232 */
233 public void setMove(String fileLanguageExpression) {
234 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
235 this.move = createFileLanguageExpression(expression);
236 }
237
238 public Expression getPreMove() {
239 return preMove;
240 }
241
242 public void setPreMove(Expression preMove) {
243 this.preMove = preMove;
244 }
245
246 /**
247 * Sets the pre move expression based on
248 * {@link org.apache.camel.language.simple.FileLanguage}
249 */
250 public void setPreMove(String fileLanguageExpression) {
251 String expression = configureMoveOrPreMoveExpression(fileLanguageExpression);
252 this.preMove = createFileLanguageExpression(expression);
253 }
254
255 public Expression getFileName() {
256 return fileName;
257 }
258
259 public void setFileName(Expression fileName) {
260 this.fileName = fileName;
261 }
262
263 /**
264 * Sets the file expression based on
265 * {@link org.apache.camel.language.simple.FileLanguage}
266 */
267 public void setFileName(String fileLanguageExpression) {
268 this.fileName = createFileLanguageExpression(fileLanguageExpression);
269 }
270
271 public Boolean isIdempotent() {
272 return idempotent != null ? idempotent : false;
273 }
274
275 boolean isIdempotentSet() {
276 return idempotent != null;
277 }
278
279 public void setIdempotent(Boolean idempotent) {
280 this.idempotent = idempotent;
281 }
282
283 public IdempotentRepository<String> getIdempotentRepository() {
284 return idempotentRepository;
285 }
286
287 public void setIdempotentRepository(IdempotentRepository<String> idempotentRepository) {
288 this.idempotentRepository = idempotentRepository;
289 }
290
291 public GenericFileFilter<T> getFilter() {
292 return filter;
293 }
294
295 public void setFilter(GenericFileFilter<T> filter) {
296 this.filter = filter;
297 }
298
299 public Comparator<GenericFile<T>> getSorter() {
300 return sorter;
301 }
302
303 public void setSorter(Comparator<GenericFile<T>> sorter) {
304 this.sorter = sorter;
305 }
306
307 public Comparator<Exchange> getSortBy() {
308 return sortBy;
309 }
310
311 public void setSortBy(Comparator<Exchange> sortBy) {
312 this.sortBy = sortBy;
313 }
314
315 public void setSortBy(String expression) {
316 setSortBy(expression, false);
317 }
318
319 public void setSortBy(String expression, boolean reverse) {
320 setSortBy(GenericFileDefaultSorter.sortByFileLanguage(getCamelContext(), expression, reverse));
321 }
322
323 public String getTempPrefix() {
324 return tempPrefix;
325 }
326
327 /**
328 * Enables and uses temporary prefix when writing files, after write it will
329 * be renamed to the correct name.
330 */
331 public void setTempPrefix(String tempPrefix) {
332 this.tempPrefix = tempPrefix;
333 // use only name as we set a prefix in from on the name
334 setTempFileName(tempPrefix + "${file:onlyname}");
335 }
336
337 public Expression getTempFileName() {
338 return tempFileName;
339 }
340
341 public void setTempFileName(Expression tempFileName) {
342 this.tempFileName = tempFileName;
343 }
344
345 public void setTempFileName(String tempFileNameExpression) {
346 this.tempFileName = createFileLanguageExpression(tempFileNameExpression);
347 }
348
349 public GenericFileConfiguration getConfiguration() {
350 if (configuration == null) {
351 configuration = new GenericFileConfiguration();
352 }
353 return configuration;
354 }
355
356 public void setConfiguration(GenericFileConfiguration configuration) {
357 this.configuration = configuration;
358 }
359
360 public GenericFileExclusiveReadLockStrategy<T> getExclusiveReadLockStrategy() {
361 return exclusiveReadLockStrategy;
362 }
363
364 public void setExclusiveReadLockStrategy(GenericFileExclusiveReadLockStrategy<T> exclusiveReadLockStrategy) {
365 this.exclusiveReadLockStrategy = exclusiveReadLockStrategy;
366 }
367
368 public String getReadLock() {
369 return readLock;
370 }
371
372 public void setReadLock(String readLock) {
373 this.readLock = readLock;
374 }
375
376 public long getReadLockTimeout() {
377 return readLockTimeout;
378 }
379
380 public void setReadLockTimeout(long readLockTimeout) {
381 this.readLockTimeout = readLockTimeout;
382 }
383
384 public int getBufferSize() {
385 return bufferSize;
386 }
387
388 public void setBufferSize(int bufferSize) {
389 if (bufferSize <= 0) {
390 throw new IllegalArgumentException("BufferSize must be a positive value, was " + bufferSize);
391 }
392 this.bufferSize = bufferSize;
393 }
394
395 public GenericFileExist getFileExist() {
396 return fileExist;
397 }
398
399 public void setFileExist(GenericFileExist fileExist) {
400 this.fileExist = fileExist;
401 }
402
403 public boolean isAutoCreate() {
404 return autoCreate;
405 }
406
407 public void setAutoCreate(boolean autoCreate) {
408 this.autoCreate = autoCreate;
409 }
410
411 public GenericFileProcessStrategy<T> getProcessStrategy() {
412 return processStrategy;
413 }
414
415 public void setProcessStrategy(GenericFileProcessStrategy<T> processStrategy) {
416 this.processStrategy = processStrategy;
417 }
418
419 public String getLocalWorkDirectory() {
420 return localWorkDirectory;
421 }
422
423 public void setLocalWorkDirectory(String localWorkDirectory) {
424 this.localWorkDirectory = localWorkDirectory;
425 }
426
427 public int getMaxMessagesPerPoll() {
428 return maxMessagesPerPoll;
429 }
430
431 public void setMaxMessagesPerPoll(int maxMessagesPerPoll) {
432 this.maxMessagesPerPoll = maxMessagesPerPoll;
433 }
434
435 public IdempotentRepository<String> getInProgressRepository() {
436 return inProgressRepository;
437 }
438
439 public void setInProgressRepository(IdempotentRepository<String> inProgressRepository) {
440 this.inProgressRepository = inProgressRepository;
441 }
442
443 public boolean isKeepLastModified() {
444 return keepLastModified;
445 }
446
447 public void setKeepLastModified(boolean keepLastModified) {
448 this.keepLastModified = keepLastModified;
449 }
450
451 /**
452 * Configures the given message with the file which sets the body to the
453 * file object.
454 */
455 public void configureMessage(GenericFile<T> file, Message message) {
456 message.setBody(file);
457
458 if (flatten) {
459 // when flatten the file name should not contain any paths
460 message.setHeader(Exchange.FILE_NAME, file.getFileNameOnly());
461 } else {
462 // compute name to set on header that should be relative to starting directory
463 String name = file.isAbsolute() ? file.getAbsoluteFilePath() : file.getRelativeFilePath();
464
465 // skip leading endpoint configured directory
466 String endpointPath = getConfiguration().getDirectory();
467 if (ObjectHelper.isNotEmpty(endpointPath) && name.startsWith(endpointPath)) {
468 name = ObjectHelper.after(name, getConfiguration().getDirectory() + File.separator);
469 }
470
471 // adjust filename
472 message.setHeader(Exchange.FILE_NAME, name);
473 }
474 }
475
476 /**
477 * Strategy to configure the move or premove option based on a String input.
478 * <p/>
479 * @param expression the original string input
480 * @return configured string or the original if no modifications is needed
481 */
482 protected String configureMoveOrPreMoveExpression(String expression) {
483 // if the expression already have ${ } placeholders then pass it unmodified
484 if (expression.indexOf("${") != -1) {
485 return expression;
486 }
487
488 // remove trailing slash
489 expression = FileUtil.stripTrailingSeparator(expression);
490
491 StringBuilder sb = new StringBuilder();
492
493 // if relative then insert start with the parent folder
494 if (!isAbsolute(expression)) {
495 sb.append("${file:parent}");
496 sb.append(getFileSeparator());
497 }
498 // insert the directory the end user provided
499 sb.append(expression);
500 // append only the filename (file:name can contain a relative path, so we must use onlyname)
501 sb.append(getFileSeparator());
502 sb.append("${file:onlyname}");
503
504 return sb.toString();
505 }
506
507 protected Map<String, Object> getParamsAsMap() {
508 Map<String, Object> params = new HashMap<String, Object>();
509
510 if (isNoop()) {
511 params.put("noop", Boolean.toString(true));
512 }
513 if (isDelete()) {
514 params.put("delete", Boolean.toString(true));
515 }
516 if (move != null) {
517 params.put("move", move);
518 }
519 if (moveFailed != null) {
520 params.put("moveFailed", moveFailed);
521 }
522 if (preMove != null) {
523 params.put("preMove", preMove);
524 }
525 if (exclusiveReadLockStrategy != null) {
526 params.put("exclusiveReadLockStrategy", exclusiveReadLockStrategy);
527 }
528 if (readLock != null) {
529 params.put("readLock", readLock);
530 }
531 if (readLockTimeout > 0) {
532 params.put("readLockTimeout", readLockTimeout);
533 }
534
535 return params;
536 }
537
538 private Expression createFileLanguageExpression(String expression) {
539 Language language;
540 // only use file language if the name is complex (eg. using $)
541 if (expression.contains("$")) {
542 language = getCamelContext().resolveLanguage("file");
543 } else {
544 language = getCamelContext().resolveLanguage("constant");
545 }
546 return language.createExpression(expression);
547 }
548 }