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.io.IOException;
021import java.io.RandomAccessFile;
022import java.nio.channels.Channel;
023import java.nio.channels.FileChannel;
024import java.nio.channels.FileLock;
025
026import org.apache.camel.Exchange;
027import org.apache.camel.LoggingLevel;
028import org.apache.camel.component.file.GenericFile;
029import org.apache.camel.component.file.GenericFileEndpoint;
030import org.apache.camel.component.file.GenericFileOperations;
031import org.apache.camel.util.CamelLogger;
032import org.apache.camel.util.IOHelper;
033import org.apache.camel.util.StopWatch;
034import org.slf4j.Logger;
035import org.slf4j.LoggerFactory;
036
037/**
038 * Acquires exclusive read lock to the given file. Will wait until the lock is granted.
039 * After granting the read lock it is released, we just want to make sure that when we start
040 * consuming the file its not currently in progress of being written by third party.
041 */
042public class FileLockExclusiveReadLockStrategy extends MarkerFileExclusiveReadLockStrategy {
043    private static final Logger LOG = LoggerFactory.getLogger(FileLockExclusiveReadLockStrategy.class);
044    private long timeout;
045    private long checkInterval = 1000;
046    private LoggingLevel readLockLoggingLevel = LoggingLevel.WARN;
047
048    @Override
049    public void prepareOnStartup(GenericFileOperations<File> operations, GenericFileEndpoint<File> endpoint) {
050        // noop
051    }
052
053    @Override
054    public boolean acquireExclusiveReadLock(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
055        // must call super
056        if (!super.acquireExclusiveReadLock(operations, file, exchange)) {
057            return false;
058        }
059
060        File target = new File(file.getAbsoluteFilePath());
061
062        LOG.trace("Waiting for exclusive read lock to file: {}", target);
063
064        FileChannel channel = null;
065        RandomAccessFile randomAccessFile = null;
066
067        boolean exclusive = false;
068        FileLock lock = null;
069
070        try {
071            randomAccessFile = new RandomAccessFile(target, "rw");
072            // try to acquire rw lock on the file before we can consume it
073            channel = randomAccessFile.getChannel();
074
075            StopWatch watch = new StopWatch();
076
077            while (!exclusive) {
078                // timeout check
079                if (timeout > 0) {
080                    long delta = watch.taken();
081                    if (delta > timeout) {
082                        CamelLogger.log(LOG, readLockLoggingLevel,
083                                "Cannot acquire read lock within " + timeout + " millis. Will skip the file: " + target);
084                        // we could not get the lock within the timeout period, so return false
085                        return false;
086                    }
087                }
088
089                // get the lock using either try lock or not depending on if we are using timeout or not
090                try {
091                    lock = timeout > 0 ? channel.tryLock() : channel.lock();
092                } catch (IllegalStateException ex) {
093                    // Also catch the OverlappingFileLockException here. Do nothing here                    
094                }
095                if (lock != null) {
096                    LOG.trace("Acquired exclusive read lock: {} to file: {}", lock, target);
097                    exclusive = true;
098                } else {
099                    boolean interrupted = sleep();
100                    if (interrupted) {
101                        // we were interrupted while sleeping, we are likely being shutdown so return false
102                        return false;
103                    }
104                }
105            }
106        } catch (IOException e) {
107            // must handle IOException as some apps on Windows etc. will still somehow hold a lock to a file
108            // such as AntiVirus or MS Office that has special locks for it's supported files
109            if (timeout == 0) {
110                // if not using timeout, then we cant retry, so return false
111                return false;
112            }
113            LOG.debug("Cannot acquire read lock. Will try again.", e);
114            boolean interrupted = sleep();
115            if (interrupted) {
116                // we were interrupted while sleeping, we are likely being shutdown so return false
117                return false;
118            }
119        } finally {
120            // close channels if we did not grab the lock
121            if (!exclusive) {
122                IOHelper.close(channel, "while acquiring exclusive read lock for file: " + target, LOG);
123                IOHelper.close(randomAccessFile, "while acquiring exclusive read lock for file: " + target, LOG);
124
125                // and also must release super lock
126                super.releaseExclusiveReadLock(operations, file, exchange);
127            }
128        }
129
130        // we grabbed the lock
131        exchange.setProperty(Exchange.FILE_LOCK_EXCLUSIVE_LOCK, lock);
132        exchange.setProperty(Exchange.FILE_LOCK_RANDOM_ACCESS_FILE, randomAccessFile);
133
134        return true;
135    }
136
137    @Override
138    public void releaseExclusiveReadLock(GenericFileOperations<File> operations,
139                                         GenericFile<File> file, Exchange exchange) throws Exception {
140
141        // must call super
142        super.releaseExclusiveReadLock(operations, file, exchange);
143
144        String target = file.getFileName();
145        FileLock lock = exchange.getProperty(Exchange.FILE_LOCK_EXCLUSIVE_LOCK, FileLock.class);
146        RandomAccessFile rac = exchange.getProperty(Exchange.FILE_LOCK_RANDOM_ACCESS_FILE, RandomAccessFile.class);
147
148        if (lock != null) {
149            Channel channel = lock.acquiredBy();
150            try {
151                lock.release();
152            } finally {
153                // close channel as well
154                IOHelper.close(channel, "while releasing exclusive read lock for file: " + target, LOG);
155                IOHelper.close(rac, "while releasing exclusive read lock for file: " + target, LOG);
156            }
157        }
158    }
159
160    private boolean sleep() {
161        LOG.trace("Exclusive read lock not granted. Sleeping for {} millis.", checkInterval);
162        try {
163            Thread.sleep(checkInterval);
164            return false;
165        } catch (InterruptedException e) {
166            LOG.debug("Sleep interrupted while waiting for exclusive read lock, so breaking out");
167            return true;
168        }
169    }
170
171    public long getTimeout() {
172        return timeout;
173    }
174
175    @Override
176    public void setTimeout(long timeout) {
177        this.timeout = timeout;
178    }
179
180    @Override
181    public void setCheckInterval(long checkInterval) {
182        this.checkInterval = checkInterval;
183    }
184
185    @Override
186    public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) {
187        this.readLockLoggingLevel = readLockLoggingLevel;
188    }
189
190}