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