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.DEBUG; 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.releaseExclusiveReadLockOnAbort(operations, file, exchange); 127 } 128 } 129 130 // store read-lock state 131 exchange.setProperty(asReadLockKey(file, Exchange.FILE_LOCK_EXCLUSIVE_LOCK), lock); 132 exchange.setProperty(asReadLockKey(file, Exchange.FILE_LOCK_RANDOM_ACCESS_FILE), randomAccessFile); 133 134 // we grabbed the lock 135 return true; 136 } 137 138 @Override 139 protected void doReleaseExclusiveReadLock(GenericFileOperations<File> operations, 140 GenericFile<File> file, Exchange exchange) throws Exception { 141 // must call super 142 super.doReleaseExclusiveReadLock(operations, file, exchange); 143 144 FileLock lock = exchange.getProperty(asReadLockKey(file, Exchange.FILE_LOCK_EXCLUSIVE_LOCK), FileLock.class); 145 RandomAccessFile rac = exchange.getProperty(asReadLockKey(file, Exchange.FILE_LOCK_EXCLUSIVE_LOCK), RandomAccessFile.class); 146 147 String target = file.getFileName(); 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 private static String asReadLockKey(GenericFile file, String key) { 191 // use the copy from absolute path as that was the original path of the file when the lock was acquired 192 // for example if the file consumer uses preMove then the file is moved and therefore has another name 193 // that would no longer match 194 String path = file.getCopyFromAbsoluteFilePath() != null ? file.getCopyFromAbsoluteFilePath() : file.getAbsoluteFilePath(); 195 return path + "-" + key; 196 } 197 198}