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