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