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.util.regex.Pattern; 021 022import org.apache.camel.Exchange; 023import org.apache.camel.LoggingLevel; 024import org.apache.camel.component.file.FileComponent; 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.GenericFileFilter; 029import org.apache.camel.component.file.GenericFileOperations; 030import org.apache.camel.util.FileUtil; 031import org.apache.camel.util.ObjectHelper; 032import org.apache.camel.util.StopWatch; 033import org.slf4j.Logger; 034import org.slf4j.LoggerFactory; 035 036/** 037 * Acquires read lock to the given file using a marker file so other Camel consumers wont acquire the same file. 038 * This is the default behavior in Camel 1.x. 039 */ 040public class MarkerFileExclusiveReadLockStrategy implements GenericFileExclusiveReadLockStrategy<File> { 041 private static final Logger LOG = LoggerFactory.getLogger(MarkerFileExclusiveReadLockStrategy.class); 042 043 private boolean markerFile = true; 044 private boolean deleteOrphanLockFiles = true; 045 046 @Override 047 public void prepareOnStartup(GenericFileOperations<File> operations, GenericFileEndpoint<File> endpoint) { 048 if (deleteOrphanLockFiles) { 049 050 String dir = endpoint.getConfiguration().getDirectory(); 051 File file = new File(dir); 052 053 LOG.debug("Prepare on startup by deleting orphaned lock files from: {}", dir); 054 055 Pattern excludePattern = endpoint.getExclude() != null ? Pattern.compile(endpoint.getExclude()) : null; 056 Pattern includePattern = endpoint.getInclude() != null ? Pattern.compile(endpoint.getInclude()) : null; 057 String endpointPath = endpoint.getConfiguration().getDirectory(); 058 059 StopWatch watch = new StopWatch(); 060 deleteLockFiles(file, endpoint.isRecursive(), endpointPath, endpoint.getFilter(), endpoint.getAntFilter(), excludePattern, includePattern); 061 062 // log anything that takes more than a second 063 if (watch.taken() > 1000) { 064 LOG.info("Prepared on startup by deleting orphaned lock files from: {} took {} millis to complete.", dir, watch.taken()); 065 } 066 } 067 } 068 069 @Override 070 public boolean acquireExclusiveReadLock(GenericFileOperations<File> operations, 071 GenericFile<File> file, Exchange exchange) throws Exception { 072 073 if (!markerFile) { 074 // if not using marker file then we assume acquired 075 return true; 076 } 077 078 String lockFileName = getLockFileName(file); 079 LOG.trace("Locking the file: {} using the lock file name: {}", file, lockFileName); 080 081 // create a plain file as marker filer for locking (do not use FileLock) 082 boolean acquired = FileUtil.createNewFile(new File(lockFileName)); 083 084 // store read-lock state 085 exchange.setProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_ACQUIRED), acquired); 086 exchange.setProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_NAME), lockFileName); 087 088 return acquired; 089 } 090 091 @Override 092 public void releaseExclusiveReadLockOnAbort(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception { 093 doReleaseExclusiveReadLock(operations, file, exchange); 094 } 095 096 @Override 097 public void releaseExclusiveReadLockOnRollback(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception { 098 doReleaseExclusiveReadLock(operations, file, exchange); 099 } 100 101 @Override 102 public void releaseExclusiveReadLockOnCommit(GenericFileOperations<File> operations, GenericFile<File> file, Exchange exchange) throws Exception { 103 doReleaseExclusiveReadLock(operations, file, exchange); 104 } 105 106 protected void doReleaseExclusiveReadLock(GenericFileOperations<File> operations, 107 GenericFile<File> file, Exchange exchange) throws Exception { 108 if (!markerFile) { 109 // if not using marker file then nothing to release 110 return; 111 } 112 113 boolean acquired = exchange.getProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_ACQUIRED), false, Boolean.class); 114 115 // only release the file if camel get the lock before 116 if (acquired) { 117 String lockFileName = exchange.getProperty(asReadLockKey(file, Exchange.FILE_LOCK_FILE_NAME), String.class); 118 File lock = new File(lockFileName); 119 120 if (lock.exists()) { 121 LOG.trace("Unlocking file: {}", lockFileName); 122 boolean deleted = FileUtil.deleteFile(lock); 123 LOG.trace("Lock file: {} was deleted: {}", lockFileName, deleted); 124 } 125 } 126 } 127 128 @Override 129 public void setTimeout(long timeout) { 130 // noop 131 } 132 133 @Override 134 public void setCheckInterval(long checkInterval) { 135 // noop 136 } 137 138 @Override 139 public void setReadLockLoggingLevel(LoggingLevel readLockLoggingLevel) { 140 // noop 141 } 142 143 @Override 144 public void setMarkerFiler(boolean markerFile) { 145 this.markerFile = markerFile; 146 } 147 148 @Override 149 public void setDeleteOrphanLockFiles(boolean deleteOrphanLockFiles) { 150 this.deleteOrphanLockFiles = deleteOrphanLockFiles; 151 } 152 153 private static void deleteLockFiles(File dir, boolean recursive, String endpointPath, 154 GenericFileFilter filter, GenericFileFilter antFilter, 155 Pattern excludePattern, Pattern includePattern) { 156 File[] files = dir.listFiles(); 157 if (files == null || files.length == 0) { 158 return; 159 } 160 161 for (File file : files) { 162 163 if (file.getName().startsWith(".")) { 164 // files starting with dot should be skipped 165 continue; 166 } 167 168 // filter unwanted files and directories to avoid traveling everything 169 if (filter != null || antFilter != null || excludePattern != null || includePattern != null) { 170 if (!acceptFile(file, endpointPath, filter, antFilter, excludePattern, includePattern)) { 171 continue; 172 } 173 } 174 175 if (file.getName().endsWith(FileComponent.DEFAULT_LOCK_FILE_POSTFIX)) { 176 LOG.warn("Deleting orphaned lock file: " + file); 177 FileUtil.deleteFile(file); 178 } else if (recursive && file.isDirectory()) { 179 deleteLockFiles(file, true, endpointPath, filter, antFilter, excludePattern, includePattern); 180 } 181 } 182 } 183 184 @SuppressWarnings("unchecked") 185 private static boolean acceptFile(File file, String endpointPath, GenericFileFilter filter, GenericFileFilter antFilter, 186 Pattern excludePattern, Pattern includePattern) { 187 GenericFile gf = new GenericFile(); 188 gf.setEndpointPath(endpointPath); 189 gf.setFile(file); 190 gf.setFileNameOnly(file.getName()); 191 gf.setFileLength(file.length()); 192 gf.setDirectory(file.isDirectory()); 193 // must use FileUtil.isAbsolute to have consistent check for whether the file is 194 // absolute or not. As windows do not consider \ paths as absolute where as all 195 // other OS platforms will consider \ as absolute. The logic in Camel mandates 196 // that we align this for all OS. That is why we must use FileUtil.isAbsolute 197 // to return a consistent answer for all OS platforms. 198 gf.setAbsolute(FileUtil.isAbsolute(file)); 199 gf.setAbsoluteFilePath(file.getAbsolutePath()); 200 gf.setLastModified(file.lastModified()); 201 202 // compute the file path as relative to the starting directory 203 File path; 204 String endpointNormalized = FileUtil.normalizePath(endpointPath); 205 if (file.getPath().startsWith(endpointNormalized + File.separator)) { 206 // skip duplicate endpoint path 207 path = new File(ObjectHelper.after(file.getPath(), endpointNormalized + File.separator)); 208 } else { 209 path = new File(file.getPath()); 210 } 211 212 if (path.getParent() != null) { 213 gf.setRelativeFilePath(path.getParent() + File.separator + file.getName()); 214 } else { 215 gf.setRelativeFilePath(path.getName()); 216 } 217 218 // the file name should be the relative path 219 gf.setFileName(gf.getRelativeFilePath()); 220 221 if (filter != null) { 222 if (!filter.accept(gf)) { 223 return false; 224 } 225 } 226 227 if (antFilter != null) { 228 if (!antFilter.accept(gf)) { 229 return false; 230 } 231 } 232 233 // exclude take precedence over include 234 if (excludePattern != null) { 235 if (excludePattern.matcher(file.getName()).matches()) { 236 return false; 237 } 238 } 239 if (includePattern != null) { 240 if (!includePattern.matcher(file.getName()).matches()) { 241 return false; 242 } 243 } 244 245 return true; 246 } 247 248 private static String getLockFileName(GenericFile<File> file) { 249 return file.getAbsoluteFilePath() + FileComponent.DEFAULT_LOCK_FILE_POSTFIX; 250 } 251 252 private static String asReadLockKey(GenericFile file, String key) { 253 // use the copy from absolute path as that was the original path of the file when the lock was acquired 254 // for example if the file consumer uses preMove then the file is moved and therefore has another name 255 // that would no longer match 256 String path = file.getCopyFromAbsoluteFilePath() != null ? file.getCopyFromAbsoluteFilePath() : file.getAbsoluteFilePath(); 257 return path + "-" + key; 258 } 259 260}