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}