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 }