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.StopWatch; 032import org.apache.camel.util.StringHelper; 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.getExcludePattern(); 056 Pattern includePattern = endpoint.getIncludePattern(); 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 171 File targetFile = file; 172 173 // if its a lock file then check if we accept its target file to know if we should delete the orphan lock file 174 if (file.getName().endsWith(FileComponent.DEFAULT_LOCK_FILE_POSTFIX)) { 175 String target = file.getName().substring(0, file.getName().length() - FileComponent.DEFAULT_LOCK_FILE_POSTFIX.length()); 176 if (file.getParent() != null) { 177 targetFile = new File(file.getParent(), target); 178 } else { 179 targetFile = new File(target); 180 } 181 } 182 183 boolean accept = acceptFile(targetFile, endpointPath, filter, antFilter, excludePattern, includePattern); 184 if (!accept) { 185 continue; 186 } 187 } 188 189 if (file.getName().endsWith(FileComponent.DEFAULT_LOCK_FILE_POSTFIX)) { 190 LOG.warn("Deleting orphaned lock file: {}", file); 191 FileUtil.deleteFile(file); 192 } else if (recursive && file.isDirectory()) { 193 deleteLockFiles(file, true, endpointPath, filter, antFilter, excludePattern, includePattern); 194 } 195 } 196 } 197 198 @SuppressWarnings("unchecked") 199 private static boolean acceptFile(File file, String endpointPath, GenericFileFilter filter, GenericFileFilter antFilter, 200 Pattern excludePattern, Pattern includePattern) { 201 GenericFile gf = new GenericFile(); 202 gf.setEndpointPath(endpointPath); 203 gf.setFile(file); 204 gf.setFileNameOnly(file.getName()); 205 gf.setFileLength(file.length()); 206 gf.setDirectory(file.isDirectory()); 207 // must use FileUtil.isAbsolute to have consistent check for whether the file is 208 // absolute or not. As windows do not consider \ paths as absolute where as all 209 // other OS platforms will consider \ as absolute. The logic in Camel mandates 210 // that we align this for all OS. That is why we must use FileUtil.isAbsolute 211 // to return a consistent answer for all OS platforms. 212 gf.setAbsolute(FileUtil.isAbsolute(file)); 213 gf.setAbsoluteFilePath(file.getAbsolutePath()); 214 gf.setLastModified(file.lastModified()); 215 216 // compute the file path as relative to the starting directory 217 File path; 218 String endpointNormalized = FileUtil.normalizePath(endpointPath); 219 if (file.getPath().startsWith(endpointNormalized + File.separator)) { 220 // skip duplicate endpoint path 221 path = new File(StringHelper.after(file.getPath(), endpointNormalized + File.separator)); 222 } else { 223 path = new File(file.getPath()); 224 } 225 226 if (path.getParent() != null) { 227 gf.setRelativeFilePath(path.getParent() + File.separator + file.getName()); 228 } else { 229 gf.setRelativeFilePath(path.getName()); 230 } 231 232 // the file name should be the relative path 233 gf.setFileName(gf.getRelativeFilePath()); 234 235 if (filter != null) { 236 // a custom filter can also filter directories 237 if (!filter.accept(gf)) { 238 return false; 239 } 240 } 241 242 // the following filters only works on files so allow any directory from this point 243 if (file.isDirectory()) { 244 return true; 245 } 246 247 if (antFilter != null) { 248 if (!antFilter.accept(gf)) { 249 return false; 250 } 251 } 252 253 // exclude take precedence over include 254 if (excludePattern != null) { 255 if (excludePattern.matcher(file.getName()).matches()) { 256 return false; 257 } 258 } 259 if (includePattern != null) { 260 if (!includePattern.matcher(file.getName()).matches()) { 261 return false; 262 } 263 } 264 265 return true; 266 } 267 268 private static String getLockFileName(GenericFile<File> file) { 269 return file.getAbsoluteFilePath() + FileComponent.DEFAULT_LOCK_FILE_POSTFIX; 270 } 271 272 private static String asReadLockKey(GenericFile file, String key) { 273 // use the copy from absolute path as that was the original path of the file when the lock was acquired 274 // for example if the file consumer uses preMove then the file is moved and therefore has another name 275 // that would no longer match 276 String path = file.getCopyFromAbsoluteFilePath() != null ? file.getCopyFromAbsoluteFilePath() : file.getAbsoluteFilePath(); 277 return path + "-" + key; 278 } 279 280}