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.FileNotFoundException;
020
021import org.apache.camel.Exchange;
022import org.apache.camel.LoggingLevel;
023import org.apache.camel.component.file.GenericFile;
024import org.apache.camel.component.file.GenericFileEndpoint;
025import org.apache.camel.component.file.GenericFileExclusiveReadLockStrategy;
026import org.apache.camel.component.file.GenericFileOperationFailedException;
027import org.apache.camel.component.file.GenericFileOperations;
028import org.apache.camel.util.CamelLogger;
029import org.apache.camel.util.StopWatch;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033/**
034 * Acquires exclusive read lock to the given file. Will wait until the lock is granted.
035 * After granting the read lock it is released, we just want to make sure that when we start
036 * consuming the file its not currently in progress of being written by third party.
037 */
038public class GenericFileRenameExclusiveReadLockStrategy<T> implements GenericFileExclusiveReadLockStrategy<T> {
039    private static final Logger LOG = LoggerFactory.getLogger(GenericFileRenameExclusiveReadLockStrategy.class);
040    private long timeout;
041    private long checkInterval;
042    private LoggingLevel readLockLoggingLevel = LoggingLevel.DEBUG;
043
044    @Override
045    public void prepareOnStartup(GenericFileOperations<T> operations, GenericFileEndpoint<T> endpoint) throws Exception {
046        // noop
047    }
048
049    @Override
050    public boolean acquireExclusiveReadLock(GenericFileOperations<T> operations, GenericFile<T> file,
051                                            Exchange exchange) throws Exception {
052        LOG.trace("Waiting for exclusive read lock to file: {}", file);
053
054        // the trick is to try to rename the file, if we can rename then we have exclusive read
055        // since its a Generic file we cannot use java.nio to get a RW lock
056        String newName = file.getFileName() + ".camelExclusiveReadLock";
057
058        // make a copy as result and change its file name
059        GenericFile<T> newFile = file.copyFrom(file);
060        newFile.changeFileName(newName);
061        StopWatch watch = new StopWatch();
062
063        boolean exclusive = false;
064        while (!exclusive) {
065            // timeout check
066            if (timeout > 0) {
067                long delta = watch.taken();
068                if (delta > timeout) {
069                    CamelLogger.log(LOG, readLockLoggingLevel,
070                            "Cannot acquire read lock within " + timeout + " millis. Will skip the file: " + file);
071                    // we could not get the lock within the timeout period, so return false
072                    return false;
073                }
074            }
075
076            try {
077                exclusive = operations.renameFile(file.getAbsoluteFilePath(), newFile.getAbsoluteFilePath());
078            } catch (GenericFileOperationFailedException ex) {
079                if (ex.getCause() instanceof FileNotFoundException) {
080                    exclusive = false;
081                } else {
082                    throw ex;
083                }
084            }
085            if (exclusive) {
086                LOG.trace("Acquired exclusive read lock to file: {}", file);
087                // rename it back so we can read it
088                operations.renameFile(newFile.getAbsoluteFilePath(), file.getAbsoluteFilePath());
089            } else {
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 true;
099    }
100
101    @Override
102    public void releaseExclusiveReadLockOnAbort(GenericFileOperations<T> operations, GenericFile<T> file, Exchange exchange) throws Exception {
103        // noop
104    }
105
106    @Override
107    public void releaseExclusiveReadLockOnRollback(GenericFileOperations<T> operations, GenericFile<T> file, Exchange exchange) throws Exception {
108        // noop
109    }
110
111    @Override
112    public void releaseExclusiveReadLockOnCommit(GenericFileOperations<T> operations, GenericFile<T> file, Exchange exchange) throws Exception {
113        // noop
114    }
115
116    private boolean sleep() {
117        LOG.trace("Exclusive read lock not granted. Sleeping for {} millis.", checkInterval);
118        try {
119            Thread.sleep(checkInterval);
120            return false;
121        } catch (InterruptedException e) {
122            LOG.debug("Sleep interrupted while waiting for exclusive read lock, so breaking out");
123            return true;
124        }
125    }
126
127    public long getTimeout() {
128        return timeout;
129    }
130
131    @Override
132    public void setTimeout(long timeout) {
133        this.timeout = timeout;
134    }
135
136    @Override
137    public void setCheckInterval(long checkInterval) {
138        this.checkInterval = checkInterval;
139    }
140
141    @Override
142    public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) {
143        this.readLockLoggingLevel = readLockLoggingLevel;
144    }
145
146    @Override
147    public void setMarkerFiler(boolean markerFile) {
148        // noop - we do not use marker file with the rename strategy
149    }
150
151    @Override
152    public void setDeleteOrphanLockFiles(boolean deleteOrphanLockFiles) {
153        // noop - we do not use marker file with the rename strategy
154    }
155}