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.strategy;
018
019import java.io.File;
020import java.util.Date;
021
022import org.apache.camel.Exchange;
023import org.apache.camel.LoggingLevel;
024import org.apache.camel.component.file.GenericFile;
025import org.apache.camel.component.file.GenericFileOperations;
026import org.apache.camel.util.CamelLogger;
027import org.apache.camel.util.StopWatch;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031/**
032 * Acquires exclusive read lock to the given file by checking whether the file is being
033 * changed by scanning the file at different intervals (to detect changes).
034 * <p/>
035 * Setting the option {@link #setMarkerFiler(boolean)} to <tt>false</tt> allows to turn off using marker files.
036 */
037public class FileChangedExclusiveReadLockStrategy extends MarkerFileExclusiveReadLockStrategy {
038    private static final Logger LOG = LoggerFactory.getLogger(FileChangedExclusiveReadLockStrategy.class);
039    private long timeout;
040    private long checkInterval = 1000;
041    private long minLength = 1;
042    private long minAge;
043    private LoggingLevel readLockLoggingLevel = LoggingLevel.DEBUG;
044
045    @Override
046    public boolean acquireExclusiveReadLock(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
047        // must call super
048        if (!super.acquireExclusiveReadLock(operations, file, exchange)) {
049            return false;
050        }
051
052        File target = new File(file.getAbsoluteFilePath());
053        boolean exclusive = false;
054
055        LOG.trace("Waiting for exclusive read lock to file: {}", file);
056
057        long lastModified = Long.MIN_VALUE;
058        long length = Long.MIN_VALUE;
059        StopWatch watch = new StopWatch();
060        long startTime = (new Date()).getTime();
061
062        while (!exclusive) {
063            // timeout check
064            if (timeout > 0) {
065                long delta = watch.taken();
066                if (delta > timeout) {
067                    CamelLogger.log(LOG, readLockLoggingLevel,
068                            "Cannot acquire read lock within " + timeout + " millis. Will skip the file: " + file);
069                    // we could not get the lock within the timeout period, so return false
070                    return false;
071                }
072            }
073
074            long newLastModified = target.lastModified();
075            long newLength = target.length();
076            long newOlderThan = startTime + watch.taken() - minAge;
077
078            LOG.trace("Previous last modified: {}, new last modified: {}", lastModified, newLastModified);
079            LOG.trace("Previous length: {}, new length: {}", length, newLength);
080            LOG.trace("New older than threshold: {}", newOlderThan);
081
082            if (newLength >= minLength && ((minAge == 0 && newLastModified == lastModified && newLength == length) || (minAge != 0 && newLastModified < newOlderThan))) {
083                LOG.trace("Read lock acquired.");
084                exclusive = true;
085            } else {
086                // set new base file change information
087                lastModified = newLastModified;
088                length = newLength;
089
090                boolean interrupted = sleep();
091                if (interrupted) {
092                    // we were interrupted while sleeping, we are likely being shutdown so return false
093                    return false;
094                }
095            }
096        }
097
098        return exclusive;
099    }
100
101    private boolean sleep() {
102        LOG.trace("Exclusive read lock not granted. Sleeping for {} millis.", checkInterval);
103        try {
104            Thread.sleep(checkInterval);
105            return false;
106        } catch (InterruptedException e) {
107            LOG.debug("Sleep interrupted while waiting for exclusive read lock, so breaking out");
108            return true;
109        }
110    }
111
112    public long getTimeout() {
113        return timeout;
114    }
115
116    @Override
117    public void setTimeout(long timeout) {
118        this.timeout = timeout;
119    }
120
121    public long getCheckInterval() {
122        return checkInterval;
123    }
124
125    @Override
126    public void setCheckInterval(long checkInterval) {
127        this.checkInterval = checkInterval;
128    }
129
130    @Override
131    public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) {
132        this.readLockLoggingLevel = readLockLoggingLevel;
133    }
134
135    public long getMinLength() {
136        return minLength;
137    }
138
139    public void setMinLength(long minLength) {
140        this.minLength = minLength;
141    }
142
143    public long getMinAge() {
144        return minAge;
145    }
146
147    public void setMinAge(long minAge) {
148        this.minAge = minAge;
149    }
150}