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}