/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.file;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.Processor;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.ObjectHelper;
/**
* File consumer.
*/
public class FileConsumer extends GenericFileConsumer<File> {
private String endpointPath;
private Set<String> extendedAttributes;
public FileConsumer(FileEndpoint endpoint, Processor processor, GenericFileOperations<File> operations) {
super(endpoint, processor, operations);
this.endpointPath = endpoint.getConfiguration().getDirectory();
if (endpoint.getExtendedAttributes() != null) {
this.extendedAttributes = new HashSet<>();
for (String attribute : endpoint.getExtendedAttributes().split(",")) {
extendedAttributes.add(attribute);
}
}
}
@Override
protected boolean pollDirectory(String fileName, List<GenericFile<File>> fileList, int depth) {
log.trace("pollDirectory from fileName: {}", fileName);
depth++;
File directory = new File(fileName);
if (!directory.exists() || !directory.isDirectory()) {
log.debug("Cannot poll as directory does not exists or its not a directory: {}", directory);
if (getEndpoint().isDirectoryMustExist()) {
throw new GenericFileOperationFailedException("Directory does not exist: " + directory);
}
return true;
}
log.trace("Polling directory: {}", directory.getPath());
File[] dirFiles = directory.listFiles();
if (dirFiles == null || dirFiles.length == 0) {
// no files in this directory to poll
if (log.isTraceEnabled()) {
log.trace("No files found in directory: {}", directory.getPath());
}
return true;
} else {
// we found some files
if (log.isTraceEnabled()) {
log.trace("Found {} in directory: {}", dirFiles.length, directory.getPath());
}
}
List<File> files = Arrays.asList(dirFiles);
for (File file : dirFiles) {
// check if we can continue polling in files
if (!canPollMoreFiles(fileList)) {
return false;
}
// trace log as Windows/Unix can have different views what the file is?
if (log.isTraceEnabled()) {
log.trace("Found file: {} [isAbsolute: {}, isDirectory: {}, isFile: {}, isHidden: {}]",
new Object[]{file, file.isAbsolute(), file.isDirectory(), file.isFile(), file.isHidden()});
}
// creates a generic file
GenericFile<File> gf = asGenericFile(endpointPath, file, getEndpoint().getCharset(), getEndpoint().isProbeContentType());
if (file.isDirectory()) {
if (endpoint.isRecursive() && depth < endpoint.getMaxDepth() && isValidFile(gf, true, files)) {
// recursive scan and add the sub files and folders
String subDirectory = fileName + File.separator + file.getName();
boolean canPollMore = pollDirectory(subDirectory, fileList, depth);
if (!canPollMore) {
return false;
}
}
} else {
// Windows can report false to a file on a share so regard it always as a file (if its not a directory)
if (depth >= endpoint.minDepth && isValidFile(gf, false, files)) {
log.trace("Adding valid file: {}", file);
// matched file so add
if (extendedAttributes != null) {
Path path = file.toPath();
Map<String, Object> allAttributes = new HashMap<>();
for (String attribute : extendedAttributes) {
try {
String prefix = null;
if (attribute.endsWith(":*")) {
prefix = attribute.substring(0, attribute.length() - 1);
} else if (attribute.equals("*")) {
prefix = "basic:";
}
if (ObjectHelper.isNotEmpty(prefix)) {
Map<String, Object> attributes = Files.readAttributes(path, attribute);
if (attributes != null) {
for (Map.Entry<String, Object> entry : attributes.entrySet()) {
allAttributes.put(prefix + entry.getKey(), entry.getValue());
}
}
} else if (!attribute.contains(":")) {
allAttributes.put("basic:" + attribute, Files.getAttribute(path, attribute));
} else {
allAttributes.put(attribute, Files.getAttribute(path, attribute));
}
} catch (IOException e) {
if (log.isDebugEnabled()) {
log.debug("Unable to read attribute {} on file {}", attribute, file, e);
}
}
}
gf.setExtendedAttributes(allAttributes);
}
fileList.add(gf);
}
}
}
return true;
}
@Override
protected boolean isMatched(GenericFile<File> file, String doneFileName, List<File> files) {
String onlyName = FileUtil.stripPath(doneFileName);
// the done file name must be among the files
for (File f : files) {
if (f.getName().equals(onlyName)) {
return true;
}
}
log.trace("Done file: {} does not exist", doneFileName);
return false;
}
/**
* Creates a new GenericFile<File> based on the given file.
*
* @param endpointPath the starting directory the endpoint was configured with
* @param file the source file
* @return wrapped as a GenericFile
* @deprecated use {@link #asGenericFile(String, File, String, boolean)}
*/
@Deprecated
public static GenericFile<File> asGenericFile(String endpointPath, File file, String charset) {
return asGenericFile(endpointPath, file, charset, false);
}
/**
* Creates a new GenericFile<File> based on the given file.
*
* @param endpointPath the starting directory the endpoint was configured with
* @param file the source file
* @param probeContentType whether to probe the content type of the file or not
* @return wrapped as a GenericFile
*/
public static GenericFile<File> asGenericFile(String endpointPath, File file, String charset, boolean probeContentType) {
GenericFile<File> answer = new GenericFile<File>(probeContentType);
// use file specific binding
answer.setBinding(new FileBinding());
answer.setCharset(charset);
answer.setEndpointPath(endpointPath);
answer.setFile(file);
answer.setFileNameOnly(file.getName());
answer.setFileLength(file.length());
answer.setDirectory(file.isDirectory());
// must use FileUtil.isAbsolute to have consistent check for whether the file is
// absolute or not. As windows do not consider \ paths as absolute where as all
// other OS platforms will consider \ as absolute. The logic in Camel mandates
// that we align this for all OS. That is why we must use FileUtil.isAbsolute
// to return a consistent answer for all OS platforms.
answer.setAbsolute(FileUtil.isAbsolute(file));
answer.setAbsoluteFilePath(file.getAbsolutePath());
answer.setLastModified(file.lastModified());
// compute the file path as relative to the starting directory
File path;
String endpointNormalized = FileUtil.normalizePath(endpointPath);
if (file.getPath().startsWith(endpointNormalized + File.separator)) {
// skip duplicate endpoint path
path = new File(ObjectHelper.after(file.getPath(), endpointNormalized + File.separator));
} else {
path = new File(file.getPath());
}
if (path.getParent() != null) {
answer.setRelativeFilePath(path.getParent() + File.separator + file.getName());
} else {
answer.setRelativeFilePath(path.getName());
}
// the file name should be the relative path
answer.setFileName(answer.getRelativeFilePath());
// use file as body as we have converters if needed as stream
answer.setBody(file);
return answer;
}
@Override
protected void updateFileHeaders(GenericFile<File> file, Message message) {
long length = file.getFile().length();
long modified = file.getFile().lastModified();
file.setFileLength(length);
file.setLastModified(modified);
if (length >= 0) {
message.setHeader(Exchange.FILE_LENGTH, length);
}
if (modified >= 0) {
message.setHeader(Exchange.FILE_LAST_MODIFIED, modified);
}
}
@Override
public FileEndpoint getEndpoint() {
return (FileEndpoint) super.getEndpoint();
}
}