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