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