/**
* 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.strategy;
import java.io.File;
import java.util.regex.Pattern;
import org.apache.camel.Exchange;
import org.apache.camel.LoggingLevel;
import org.apache.camel.component.file.FileComponent;
import org.apache.camel.component.file.GenericFile;
import org.apache.camel.component.file.GenericFileEndpoint;
import org.apache.camel.component.file.GenericFileExclusiveReadLockStrategy;
import org.apache.camel.component.file.GenericFileFilter;
import org.apache.camel.component.file.GenericFileOperations;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.util.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Acquires read lock to the given file using a marker file so other Camel consumers wont acquire the same file.
* This is the default behavior in Camel 1.x.
*/
public class MarkerFileExclusiveReadLockStrategy implements GenericFileExclusiveReadLockStrategy<File> {
private static final Logger LOG = LoggerFactory.getLogger(MarkerFileExclusiveReadLockStrategy.class);
private boolean markerFile = true;
private boolean deleteOrphanLockFiles = true;
@Override
public void prepareOnStartup(GenericFileOperations<File> operations, GenericFileEndpoint<File> endpoint) {
if (deleteOrphanLockFiles) {
String dir = endpoint.getConfiguration().getDirectory();
File file = new File(dir);
LOG.debug("Prepare on startup by deleting orphaned lock files from: {}", dir);
Pattern excludePattern = endpoint.getExcludePattern();
Pattern includePattern = endpoint.getIncludePattern();
String endpointPath = endpoint.getConfiguration().getDirectory();
StopWatch watch = new StopWatch();
deleteLockFiles(file, endpoint.isRecursive(), endpointPath, endpoint.getFilter(), endpoint.getAntFilter(), excludePattern, includePattern);
// log anything that takes more than a second
if (watch.taken() > 1000) {
LOG.info("Prepared on startup by deleting orphaned lock files from: {} took {} millis to complete.", dir, watch.taken());
}
}
}
@Override
public boolean acquireExclusiveReadLock(GenericFileOperations<File> operations,
GenericFile<File> file, Exchange exchange) throws Exception {
if (!markerFile) {
// if not using marker file then we assume acquired
return true;
}
String lockFileName = getLockFileName(file);
LOG.trace("Locking the file: {} using the lock file name: {}", file, lockFileName);
// create a plain file as marker filer for locking (do not use FileLock)
boolean acquired = FileUtil.createNewFile(new File(lockFileName));
// store read-lock state
exchange.setProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_ACQUIRED), acquired);
exchange.setProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_NAME), lockFileName);
return acquired;
}
@Override
public void releaseExclusiveReadLockOnAbort(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
doReleaseExclusiveReadLock(operations, file, exchange);
}
@Override
public void releaseExclusiveReadLockOnRollback(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
doReleaseExclusiveReadLock(operations, file, exchange);
}
@Override
public void releaseExclusiveReadLockOnCommit(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
doReleaseExclusiveReadLock(operations, file, exchange);
}
protected void doReleaseExclusiveReadLock(GenericFileOperations<File> operations,
GenericFile<File> file, Exchange exchange) throws Exception {
if (!markerFile) {
// if not using marker file then nothing to release
return;
}
boolean acquired = exchange.getProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_ACQUIRED), false, Boolean.class);
// only release the file if camel get the lock before
if (acquired) {
String lockFileName = exchange.getProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_NAME), String.class);
File lock = new File(lockFileName);
if (lock.exists()) {
LOG.trace("Unlocking file: {}", lockFileName);
boolean deleted = FileUtil.deleteFile(lock);
LOG.trace("Lock file: {} was deleted: {}", lockFileName, deleted);
}
}
}
@Override
public void setTimeout(long timeout) {
// noop
}
@Override
public void setCheckInterval(long checkInterval) {
// noop
}
@Override
public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) {
// noop
}
@Override
public void setMarkerFiler(boolean markerFile) {
this.markerFile = markerFile;
}
@Override
public void setDeleteOrphanLockFiles(boolean deleteOrphanLockFiles) {
this.deleteOrphanLockFiles = deleteOrphanLockFiles;
}
private static void deleteLockFiles(File dir, boolean recursive, String endpointPath,
GenericFileFilter filter, GenericFileFilter antFilter,
Pattern excludePattern, Pattern includePattern) {
File[] files = dir.listFiles();
if (files == null || files.length == 0) {
return;
}
for (File file : files) {
if (file.getName().startsWith(".")) {
// files starting with dot should be skipped
continue;
}
// filter unwanted files and directories to avoid traveling everything
if (filter != null || antFilter != null || excludePattern != null || includePattern != null) {
if (!acceptFile(file, endpointPath, filter, antFilter, excludePattern, includePattern)) {
continue;
}
}
if (file.getName().endsWith(FileComponent.DEFAULT_LOCK_FILE_POSTFIX)) {
LOG.warn("Deleting orphaned lock file: " + file);
FileUtil.deleteFile(file);
} else if (recursive && file.isDirectory()) {
deleteLockFiles(file, true, endpointPath, filter, antFilter, excludePattern, includePattern);
}
}
}
@SuppressWarnings("unchecked")
private static boolean acceptFile(File file, String endpointPath, GenericFileFilter filter, GenericFileFilter antFilter,
Pattern excludePattern, Pattern includePattern) {
GenericFile gf = new GenericFile();
gf.setEndpointPath(endpointPath);
gf.setFile(file);
gf.setFileNameOnly(file.getName());
gf.setFileLength(file.length());
gf.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.
gf.setAbsolute(FileUtil.isAbsolute(file));
gf.setAbsoluteFilePath(file.getAbsolutePath());
gf.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) {
gf.setRelativeFilePath(path.getParent() + File.separator + file.getName());
} else {
gf.setRelativeFilePath(path.getName());
}
// the file name should be the relative path
gf.setFileName(gf.getRelativeFilePath());
if (filter != null) {
if (!filter.accept(gf)) {
return false;
}
}
if (antFilter != null) {
if (!antFilter.accept(gf)) {
return false;
}
}
// exclude take precedence over include
if (excludePattern != null) {
if (excludePattern.matcher(file.getName()).matches()) {
return false;
}
}
if (includePattern != null) {
if (!includePattern.matcher(file.getName()).matches()) {
return false;
}
}
return true;
}
private static String getLockFileName(GenericFile<File> file) {
return file.getAbsoluteFilePath() + FileComponent.DEFAULT_LOCK_FILE_POSTFIX;
}
private static String asReadLockKey(GenericFile file, String key) {
// use the copy from absolute path as that was the original path of the file when the lock was acquired
// for example if the file consumer uses preMove then the file is moved and therefore has another name
// that would no longer match
String path = file.getCopyFromAbsoluteFilePath() != null ? file.getCopyFromAbsoluteFilePath() : file.getAbsoluteFilePath();
return path + "-" + key;
}
}