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 */ 017package org.apache.camel.component.file; 018 019import java.io.File; 020import java.io.IOException; 021import java.nio.file.Files; 022import java.nio.file.Path; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030 031import org.apache.camel.Exchange; 032import org.apache.camel.Message; 033import org.apache.camel.Processor; 034import org.apache.camel.util.FileUtil; 035import org.apache.camel.util.ObjectHelper; 036import org.apache.camel.util.StringHelper; 037 038/** 039 * File consumer. 040 */ 041public class FileConsumer extends GenericFileConsumer<File> { 042 043 private String endpointPath; 044 private Set<String> extendedAttributes; 045 046 public FileConsumer(FileEndpoint endpoint, Processor processor, GenericFileOperations<File> operations, GenericFileProcessStrategy<File> processStrategy) { 047 super(endpoint, processor, operations, processStrategy); 048 this.endpointPath = endpoint.getConfiguration().getDirectory(); 049 050 if (endpoint.getExtendedAttributes() != null) { 051 this.extendedAttributes = new HashSet<>(); 052 053 for (String attribute : endpoint.getExtendedAttributes().split(",")) { 054 extendedAttributes.add(attribute); 055 } 056 } 057 } 058 059 @Override 060 protected boolean pollDirectory(String fileName, List<GenericFile<File>> fileList, int depth) { 061 log.trace("pollDirectory from fileName: {}", fileName); 062 063 depth++; 064 065 File directory = new File(fileName); 066 if (!directory.exists() || !directory.isDirectory()) { 067 log.debug("Cannot poll as directory does not exists or its not a directory: {}", directory); 068 if (getEndpoint().isDirectoryMustExist()) { 069 throw new GenericFileOperationFailedException("Directory does not exist: " + directory); 070 } 071 return true; 072 } 073 074 log.trace("Polling directory: {}", directory.getPath()); 075 File[] dirFiles = directory.listFiles(); 076 if (dirFiles == null || dirFiles.length == 0) { 077 // no files in this directory to poll 078 if (log.isTraceEnabled()) { 079 log.trace("No files found in directory: {}", directory.getPath()); 080 } 081 return true; 082 } else { 083 // we found some files 084 if (log.isTraceEnabled()) { 085 log.trace("Found {} in directory: {}", dirFiles.length, directory.getPath()); 086 } 087 } 088 List<File> files = Arrays.asList(dirFiles); 089 if (getEndpoint().isPreSort()) { 090 Collections.sort(files, (a, b) -> a.getAbsoluteFile().compareTo(a.getAbsoluteFile())); 091 } 092 093 for (File file : dirFiles) { 094 // check if we can continue polling in files 095 if (!canPollMoreFiles(fileList)) { 096 return false; 097 } 098 099 // trace log as Windows/Unix can have different views what the file is? 100 if (log.isTraceEnabled()) { 101 log.trace("Found file: {} [isAbsolute: {}, isDirectory: {}, isFile: {}, isHidden: {}]", 102 new Object[]{file, file.isAbsolute(), file.isDirectory(), file.isFile(), file.isHidden()}); 103 } 104 105 // creates a generic file 106 GenericFile<File> gf = asGenericFile(endpointPath, file, getEndpoint().getCharset(), getEndpoint().isProbeContentType()); 107 108 if (file.isDirectory()) { 109 if (endpoint.isRecursive() && depth < endpoint.getMaxDepth() && isValidFile(gf, true, files)) { 110 // recursive scan and add the sub files and folders 111 String subDirectory = fileName + File.separator + file.getName(); 112 boolean canPollMore = pollDirectory(subDirectory, fileList, depth); 113 if (!canPollMore) { 114 return false; 115 } 116 } 117 } else { 118 // Windows can report false to a file on a share so regard it always as a file (if its not a directory) 119 if (depth >= endpoint.minDepth && isValidFile(gf, false, files)) { 120 log.trace("Adding valid file: {}", file); 121 // matched file so add 122 if (extendedAttributes != null) { 123 Path path = file.toPath(); 124 Map<String, Object> allAttributes = new HashMap<>(); 125 for (String attribute : extendedAttributes) { 126 try { 127 String prefix = null; 128 if (attribute.endsWith(":*")) { 129 prefix = attribute.substring(0, attribute.length() - 1); 130 } else if (attribute.equals("*")) { 131 prefix = "basic:"; 132 } 133 134 if (ObjectHelper.isNotEmpty(prefix)) { 135 Map<String, Object> attributes = Files.readAttributes(path, attribute); 136 if (attributes != null) { 137 for (Map.Entry<String, Object> entry : attributes.entrySet()) { 138 allAttributes.put(prefix + entry.getKey(), entry.getValue()); 139 } 140 } 141 } else if (!attribute.contains(":")) { 142 allAttributes.put("basic:" + attribute, Files.getAttribute(path, attribute)); 143 } else { 144 allAttributes.put(attribute, Files.getAttribute(path, attribute)); 145 } 146 } catch (IOException e) { 147 if (log.isDebugEnabled()) { 148 log.debug("Unable to read attribute {} on file {}", attribute, file, e); 149 } 150 } 151 } 152 153 gf.setExtendedAttributes(allAttributes); 154 } 155 156 fileList.add(gf); 157 } 158 159 } 160 } 161 162 return true; 163 } 164 165 @Override 166 protected boolean isMatched(GenericFile<File> file, String doneFileName, List<File> files) { 167 String onlyName = FileUtil.stripPath(doneFileName); 168 // the done file name must be among the files 169 for (File f : files) { 170 if (f.getName().equals(onlyName)) { 171 return true; 172 } 173 } 174 log.trace("Done file: {} does not exist", doneFileName); 175 return false; 176 } 177 178 /** 179 * Creates a new GenericFile<File> based on the given file. 180 * 181 * @param endpointPath the starting directory the endpoint was configured with 182 * @param file the source file 183 * @return wrapped as a GenericFile 184 * @deprecated use {@link #asGenericFile(String, File, String, boolean)} 185 */ 186 @Deprecated 187 public static GenericFile<File> asGenericFile(String endpointPath, File file, String charset) { 188 return asGenericFile(endpointPath, file, charset, false); 189 } 190 191 /** 192 * Creates a new GenericFile<File> based on the given file. 193 * 194 * @param endpointPath the starting directory the endpoint was configured with 195 * @param file the source file 196 * @param probeContentType whether to probe the content type of the file or not 197 * @return wrapped as a GenericFile 198 */ 199 public static GenericFile<File> asGenericFile(String endpointPath, File file, String charset, boolean probeContentType) { 200 GenericFile<File> answer = new GenericFile<>(probeContentType); 201 // use file specific binding 202 answer.setBinding(new FileBinding()); 203 204 answer.setCharset(charset); 205 answer.setEndpointPath(endpointPath); 206 answer.setFile(file); 207 answer.setFileNameOnly(file.getName()); 208 answer.setFileLength(file.length()); 209 answer.setDirectory(file.isDirectory()); 210 // must use FileUtil.isAbsolute to have consistent check for whether the file is 211 // absolute or not. As windows do not consider \ paths as absolute where as all 212 // other OS platforms will consider \ as absolute. The logic in Camel mandates 213 // that we align this for all OS. That is why we must use FileUtil.isAbsolute 214 // to return a consistent answer for all OS platforms. 215 answer.setAbsolute(FileUtil.isAbsolute(file)); 216 answer.setAbsoluteFilePath(file.getAbsolutePath()); 217 answer.setLastModified(file.lastModified()); 218 219 // compute the file path as relative to the starting directory 220 File path; 221 String endpointNormalized = FileUtil.normalizePath(endpointPath); 222 if (file.getPath().startsWith(endpointNormalized + File.separator)) { 223 // skip duplicate endpoint path 224 path = new File(StringHelper.after(file.getPath(), endpointNormalized + File.separator)); 225 } else { 226 path = new File(file.getPath()); 227 } 228 229 if (path.getParent() != null) { 230 answer.setRelativeFilePath(path.getParent() + File.separator + file.getName()); 231 } else { 232 answer.setRelativeFilePath(path.getName()); 233 } 234 235 // the file name should be the relative path 236 answer.setFileName(answer.getRelativeFilePath()); 237 238 // use file as body as we have converters if needed as stream 239 answer.setBody(file); 240 return answer; 241 } 242 243 @Override 244 protected void updateFileHeaders(GenericFile<File> file, Message message) { 245 File upToDateFile = file.getFile(); 246 if (fileHasMoved(file)) { 247 upToDateFile = new File(file.getAbsoluteFilePath()); 248 } 249 long length = upToDateFile.length(); 250 long modified = upToDateFile.lastModified(); 251 file.setFileLength(length); 252 file.setLastModified(modified); 253 if (length >= 0) { 254 message.setHeader(Exchange.FILE_LENGTH, length); 255 } 256 if (modified >= 0) { 257 message.setHeader(Exchange.FILE_LAST_MODIFIED, modified); 258 } 259 } 260 261 @Override 262 public FileEndpoint getEndpoint() { 263 return (FileEndpoint) super.getEndpoint(); 264 } 265 266 private boolean fileHasMoved(GenericFile<File> file) { 267 // GenericFile's absolute path is always up to date whereas the underlying file is not 268 return !file.getFile().getAbsolutePath().equals(file.getAbsoluteFilePath()); 269 } 270}