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     */
017    package org.apache.camel.component.file.strategy;
018    
019    import java.io.File;
020    import java.io.IOException;
021    import java.io.RandomAccessFile;
022    import java.nio.channels.Channel;
023    import java.nio.channels.FileChannel;
024    import java.nio.channels.FileLock;
025    
026    import org.apache.camel.Exchange;
027    import org.apache.camel.component.file.GenericFile;
028    import org.apache.camel.component.file.GenericFileEndpoint;
029    import org.apache.camel.component.file.GenericFileExclusiveReadLockStrategy;
030    import org.apache.camel.component.file.GenericFileOperations;
031    import org.apache.camel.util.ExchangeHelper;
032    import org.apache.camel.util.ObjectHelper;
033    import org.apache.commons.logging.Log;
034    import org.apache.commons.logging.LogFactory;
035    
036    /**
037     * Acquires exclusive read lock to the given file. Will wait until the lock is granted.
038     * After granting the read lock it is released, we just want to make sure that when we start
039     * consuming the file its not currently in progress of being written by third party.
040     */
041    public class FileLockExclusiveReadLockStrategy implements GenericFileExclusiveReadLockStrategy<File> {
042        private static final transient Log LOG = LogFactory.getLog(FileLockExclusiveReadLockStrategy.class);
043        private long timeout;
044    
045        public void prepareOnStartup(GenericFileOperations<File> operations, GenericFileEndpoint<File> endpoint) {
046            // noop
047        }
048    
049        public boolean acquireExclusiveReadLock(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
050            File target = new File(file.getAbsoluteFilePath());
051    
052            if (LOG.isTraceEnabled()) {
053                LOG.trace("Waiting for exclusive read lock to file: " + target);
054            }
055    
056            try {
057                // try to acquire rw lock on the file before we can consume it
058                FileChannel channel = new RandomAccessFile(target, "rw").getChannel();
059    
060                long start = System.currentTimeMillis();
061                boolean exclusive = false;
062    
063                while (!exclusive) {
064                    // timeout check
065                    if (timeout > 0) {
066                        long delta = System.currentTimeMillis() - start;
067                        if (delta > timeout) {
068                            LOG.warn("Cannot acquire read lock within " + timeout + " millis. Will skip the file: " + target);
069                            // we could not get the lock within the timeout period, so return false
070                            return false;
071                        }
072                    }
073    
074                    // get the lock using either try lock or not depending on if we are using timeout or not
075                    FileLock lock = null;
076                    try {
077                        lock = timeout > 0 ? channel.tryLock() : channel.lock();
078                    } catch (IllegalStateException ex) {
079                        // Also catch the OverlappingFileLockException here. Do nothing here                    
080                    }
081                    if (lock != null) {
082                        if (LOG.isTraceEnabled()) {
083                            LOG.trace("Acquired exclusive read lock: " + lock + " to file: " + target);
084                        }
085    
086                        // store lock so we can release it later
087                        exchange.setProperty("CamelFileLock", lock);
088                        exchange.setProperty("CamelFileLockName", target.getName());
089    
090                        exclusive = true;
091                    } else {
092                        boolean interrupted = sleep();
093                        if (interrupted) {
094                            // we were interrupted while sleeping, we are likely being shutdown so return false
095                            return false;
096                        }
097                    }
098                }
099            } catch (IOException e) {
100                // must handle IOException as some apps on Windows etc. will still somehow hold a lock to a file
101                // such as AntiVirus or MS Office that has special locks for it's supported files
102                if (timeout == 0) {
103                    // if not using timeout, then we cant retry, so rethrow
104                    throw e;
105                }
106                if (LOG.isDebugEnabled()) {
107                    LOG.debug("Cannot acquire read lock. Will try again.", e);
108                }
109                boolean interrupted = sleep();
110                if (interrupted) {
111                    // we were interrupted while sleeping, we are likely being shutdown so return false
112                    return false;
113                }
114            }
115    
116            return true;
117        }
118    
119        public void releaseExclusiveReadLock(GenericFileOperations<File> operations,
120                                             GenericFile<File> file, Exchange exchange) throws Exception {
121            FileLock lock = ExchangeHelper.getMandatoryProperty(exchange, "CamelFileLock", FileLock.class);
122            String lockFileName = ExchangeHelper.getMandatoryProperty(exchange, "CamelFileLockName", String.class);
123            Channel channel = lock.channel();
124            try {
125                lock.release();
126            } finally {
127                // must close channel first
128                ObjectHelper.close(channel, "while acquiring exclusive read lock for file: " + lockFileName, LOG);
129            }
130        }
131    
132        private boolean sleep() {
133            if (LOG.isTraceEnabled()) {
134                LOG.trace("Exclusive read lock not granted. Sleeping for 1000 millis.");
135            }
136            try {
137                Thread.sleep(1000);
138                return false;
139            } catch (InterruptedException e) {
140                if (LOG.isDebugEnabled()) {
141                    LOG.debug("Sleep interrupted while waiting for exclusive read lock, so breaking out");
142                }
143                return true;
144            }
145        }
146    
147        public long getTimeout() {
148            return timeout;
149        }
150    
151        /**
152         * Sets an optional timeout period.
153         * <p/>
154         * If the readlock could not be granted within the timeperiod then the wait is stopped and the
155         * acquireReadLock returns <tt>false</tt>.
156         *
157         * @param timeout period in millis
158         */
159        public void setTimeout(long timeout) {
160            this.timeout = timeout;
161        }
162    
163    }