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;
020
021import org.apache.camel.CamelContext;
022import org.apache.camel.CamelContextAware;
023import org.apache.camel.Exchange;
024import org.apache.camel.LoggingLevel;
025import org.apache.camel.component.file.GenericFile;
026import org.apache.camel.component.file.GenericFileEndpoint;
027import org.apache.camel.component.file.GenericFileExclusiveReadLockStrategy;
028import org.apache.camel.component.file.GenericFileOperations;
029import org.apache.camel.spi.IdempotentRepository;
030import org.apache.camel.support.ServiceSupport;
031import org.apache.camel.util.CamelLogger;
032import org.apache.camel.util.ObjectHelper;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * A file read lock that uses an {@link IdempotentRepository} and {@link FileRenameExclusiveReadLockStrategy rename} as the lock strategy.
038 * This allows to plugin and use existing idempotent repositories that for example supports clustering.
039 * The other read lock strategies that are using marker files or file locks, are not guaranteed to work in clustered setup with various platform and file systems.
040 */
041public class FileIdempotentRenameRepositoryReadLockStrategy extends ServiceSupport implements GenericFileExclusiveReadLockStrategy<File>, CamelContextAware {
042
043    private static final transient Logger LOG = LoggerFactory.getLogger(FileIdempotentRenameRepositoryReadLockStrategy.class);
044
045    private final FileRenameExclusiveReadLockStrategy rename;
046    private GenericFileEndpoint<File> endpoint;
047    private LoggingLevel readLockLoggingLevel = LoggingLevel.DEBUG;
048    private CamelContext camelContext;
049    private IdempotentRepository<String> idempotentRepository;
050    private boolean removeOnRollback = true;
051    private boolean removeOnCommit;
052
053    public FileIdempotentRenameRepositoryReadLockStrategy() {
054        this.rename = new FileRenameExclusiveReadLockStrategy();
055        // no need to use marker file as idempotent ensures exclusive read-lock
056        this.rename.setMarkerFiler(false);
057        this.rename.setDeleteOrphanLockFiles(false);
058    }
059
060    @Override
061    public void prepareOnStartup(GenericFileOperations<File> operations, GenericFileEndpoint<File> endpoint) throws Exception {
062        this.endpoint = endpoint;
063        LOG.info("Using FileIdempotentRepositoryReadLockStrategy: {} on endpoint: {}", idempotentRepository, endpoint);
064
065        rename.prepareOnStartup(operations, endpoint);
066    }
067
068    @Override
069    public boolean acquireExclusiveReadLock(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
070        // in clustered mode then another node may have processed the file so we must check here again if the file exists
071        File path = file.getFile();
072        if (!path.exists()) {
073            return false;
074        }
075
076        // check if we can begin on this file
077        String key = asKey(file);
078        boolean answer = idempotentRepository.add(key);
079        if (!answer) {
080            // another node is processing the file so skip
081            CamelLogger.log(LOG, readLockLoggingLevel, "Cannot acquire read lock. Will skip the file: " + file);
082        }
083
084        if (answer) {
085            // if we acquired during idempotent then check rename also
086            answer = rename.acquireExclusiveReadLock(operations, file, exchange);
087            if (!answer) {
088                // remove from idempontent as we did not acquire it from changed
089                idempotentRepository.remove(key);
090            }
091        }
092        return answer;
093    }
094
095    @Override
096    public void releaseExclusiveReadLockOnAbort(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
097        rename.releaseExclusiveReadLockOnAbort(operations, file, exchange);
098    }
099
100    @Override
101    public void releaseExclusiveReadLockOnRollback(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
102        String key = asKey(file);
103        if (removeOnRollback) {
104            idempotentRepository.remove(key);
105        } else {
106            // okay we should not remove then confirm it instead
107            idempotentRepository.confirm(key);
108        }
109
110        rename.releaseExclusiveReadLockOnRollback(operations, file, exchange);
111    }
112
113    @Override
114    public void releaseExclusiveReadLockOnCommit(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception {
115        String key = asKey(file);
116        if (removeOnCommit) {
117            idempotentRepository.remove(key);
118        } else {
119            // confirm on commit
120            idempotentRepository.confirm(key);
121        }
122
123        rename.releaseExclusiveReadLockOnCommit(operations, file, exchange);
124    }
125
126    public void setTimeout(long timeout) {
127        rename.setTimeout(timeout);
128    }
129
130    public void setCheckInterval(long checkInterval) {
131        rename.setCheckInterval(checkInterval);
132    }
133
134    public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) {
135        this.readLockLoggingLevel = readLockLoggingLevel;
136        rename.setReadLockLoggingLevel(readLockLoggingLevel);
137    }
138
139    public void setMarkerFiler(boolean markerFile) {
140        // we do not use marker files
141    }
142
143    public void setDeleteOrphanLockFiles(boolean deleteOrphanLockFiles) {
144        // we do not use marker files
145    }
146
147    public CamelContext getCamelContext() {
148        return camelContext;
149    }
150
151    public void setCamelContext(CamelContext camelContext) {
152        this.camelContext = camelContext;
153    }
154
155    /**
156     * The idempotent repository to use as the store for the read locks.
157     */
158    public IdempotentRepository<String> getIdempotentRepository() {
159        return idempotentRepository;
160    }
161
162    /**
163     * The idempotent repository to use as the store for the read locks.
164     */
165    public void setIdempotentRepository(IdempotentRepository<String> idempotentRepository) {
166        this.idempotentRepository = idempotentRepository;
167    }
168
169    /**
170     * Whether to remove the file from the idempotent repository when doing a rollback.
171     * <p/>
172     * By default this is true.
173     */
174    public boolean isRemoveOnRollback() {
175        return removeOnRollback;
176    }
177
178    /**
179     * Whether to remove the file from the idempotent repository when doing a rollback.
180     * <p/>
181     * By default this is true.
182     */
183    public void setRemoveOnRollback(boolean removeOnRollback) {
184        this.removeOnRollback = removeOnRollback;
185    }
186
187    /**
188     * Whether to remove the file from the idempotent repository when doing a commit.
189     * <p/>
190     * By default this is false.
191     */
192    public boolean isRemoveOnCommit() {
193        return removeOnCommit;
194    }
195
196    /**
197     * Whether to remove the file from the idempotent repository when doing a commit.
198     * <p/>
199     * By default this is false.
200     */
201    public void setRemoveOnCommit(boolean removeOnCommit) {
202        this.removeOnCommit = removeOnCommit;
203    }
204
205    protected String asKey(GenericFile<File> file) {
206        // use absolute file path as default key, but evaluate if an expression key was configured
207        String key = file.getAbsoluteFilePath();
208        if (endpoint.getIdempotentKey() != null) {
209            Exchange dummy = endpoint.createExchange(file);
210            key = endpoint.getIdempotentKey().evaluate(dummy, String.class);
211        }
212        return key;
213    }
214
215    @Override
216    protected void doStart() throws Exception {
217        ObjectHelper.notNull(camelContext, "camelContext", this);
218        ObjectHelper.notNull(idempotentRepository, "idempotentRepository", this);
219
220        // ensure the idempotent repository is added as a service so CamelContext will stop the repo when it shutdown itself
221        camelContext.addService(idempotentRepository, true);
222    }
223
224    @Override
225    protected void doStop() throws Exception {
226        // noop
227    }
228
229}