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.commons.vfs2.cache; 018 019import java.lang.ref.Reference; 020import java.lang.ref.ReferenceQueue; 021import java.lang.ref.SoftReference; 022import java.util.HashMap; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.concurrent.ConcurrentHashMap; 026import java.util.concurrent.ConcurrentMap; 027import java.util.concurrent.locks.Lock; 028import java.util.concurrent.locks.ReentrantLock; 029 030import org.apache.commons.logging.Log; 031import org.apache.commons.logging.LogFactory; 032import org.apache.commons.vfs2.FileName; 033import org.apache.commons.vfs2.FileObject; 034import org.apache.commons.vfs2.FileSystem; 035import org.apache.commons.vfs2.VfsLog; 036import org.apache.commons.vfs2.util.Messages; 037 038/** 039 * This implementation caches every file as long as it is strongly reachable by the java vm. As soon as the vm needs 040 * memory - every softly reachable file will be discarded. 041 * 042 * @see SoftReference 043 */ 044public class SoftRefFilesCache extends AbstractFilesCache { 045 private static final int TIMEOUT = 1000; 046 047 private static final Log log = LogFactory.getLog(SoftRefFilesCache.class); 048 049 private final ConcurrentMap<FileSystem, Map<FileName, Reference<FileObject>>> fileSystemCache = new ConcurrentHashMap<>(); 050 private final Map<Reference<FileObject>, FileSystemAndNameKey> refReverseMap = new HashMap<>(100); 051 private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<>(); 052 053 private volatile SoftRefReleaseThread softRefReleaseThread = null; // @GuardedBy("lock") 054 055 private final Lock lock = new ReentrantLock(); 056 057 /** 058 * This thread will listen on the ReferenceQueue and remove the entry in the filescache as soon as the vm removes 059 * the reference 060 */ 061 private final class SoftRefReleaseThread extends Thread { 062 private volatile boolean requestEnd; // used for inter-thread communication 063 064 private SoftRefReleaseThread() { 065 setName(SoftRefReleaseThread.class.getName()); 066 setDaemon(true); 067 } 068 069 @Override 070 public void run() { 071 loop: while (!requestEnd && !Thread.currentThread().isInterrupted()) { 072 try { 073 final Reference<?> ref = refQueue.remove(TIMEOUT); 074 if (ref == null) { 075 continue; 076 } 077 078 lock.lock(); 079 try { 080 final FileSystemAndNameKey key = refReverseMap.get(ref); 081 082 if (key != null && removeFile(key)) { 083 close(key.getFileSystem()); 084 } 085 } finally { 086 lock.unlock(); 087 } 088 } catch (final InterruptedException e) { 089 if (!requestEnd) { 090 VfsLog.warn(getLogger(), log, 091 Messages.getString("vfs.impl/SoftRefReleaseThread-interrupt.info")); 092 } 093 break loop; 094 } 095 } 096 } 097 } 098 099 public SoftRefFilesCache() { 100 } 101 102 private void startThread() { 103 // Double Checked Locking is allowed when volatile 104 if (softRefReleaseThread != null) { 105 return; 106 } 107 108 synchronized (lock) { 109 if (softRefReleaseThread == null) { 110 softRefReleaseThread = new SoftRefReleaseThread(); 111 softRefReleaseThread.start(); 112 } 113 } 114 } 115 116 private void endThread() { 117 synchronized (lock) { 118 final SoftRefReleaseThread thread = softRefReleaseThread; 119 softRefReleaseThread = null; 120 if (thread != null) { 121 thread.requestEnd = true; 122 thread.interrupt(); 123 } 124 } 125 } 126 127 @Override 128 public void putFile(final FileObject fileObject) { 129 if (log.isDebugEnabled()) { 130 log.debug("putFile: " + this.getSafeName(fileObject)); 131 } 132 133 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem()); 134 135 final Reference<FileObject> ref = createReference(fileObject, refQueue); 136 final FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName()); 137 138 lock.lock(); 139 try { 140 final Reference<FileObject> old = files.put(fileObject.getName(), ref); 141 if (old != null) { 142 refReverseMap.remove(old); 143 } 144 refReverseMap.put(ref, key); 145 } finally { 146 lock.unlock(); 147 } 148 } 149 150 private String getSafeName(final FileName fileName) { 151 return fileName.getFriendlyURI(); 152 } 153 154 private String getSafeName(final FileObject fileObject) { 155 return this.getSafeName(fileObject.getName()); 156 } 157 158 @Override 159 public boolean putFileIfAbsent(final FileObject fileObject) { 160 if (log.isDebugEnabled()) { 161 log.debug("putFile: " + this.getSafeName(fileObject)); 162 } 163 164 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem()); 165 166 final Reference<FileObject> ref = createReference(fileObject, refQueue); 167 final FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName()); 168 169 lock.lock(); 170 try { 171 if (files.containsKey(fileObject.getName()) && files.get(fileObject.getName()).get() != null) { 172 return false; 173 } 174 final Reference<FileObject> old = files.put(fileObject.getName(), ref); 175 if (old != null) { 176 refReverseMap.remove(old); 177 } 178 refReverseMap.put(ref, key); 179 return true; 180 } finally { 181 lock.unlock(); 182 } 183 } 184 185 protected Reference<FileObject> createReference(final FileObject file, final ReferenceQueue<FileObject> refqueue) { 186 return new SoftReference<>(file, refqueue); 187 } 188 189 @Override 190 public FileObject getFile(final FileSystem fileSystem, final FileName fileName) { 191 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem); 192 193 lock.lock(); 194 try { 195 final Reference<FileObject> ref = files.get(fileName); 196 if (ref == null) { 197 return null; 198 } 199 200 final FileObject fo = ref.get(); 201 if (fo == null) { 202 removeFile(fileSystem, fileName); 203 } 204 return fo; 205 } finally { 206 lock.unlock(); 207 } 208 } 209 210 @Override 211 public void clear(final FileSystem fileSystem) { 212 final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem); 213 214 lock.lock(); 215 try { 216 final Iterator<FileSystemAndNameKey> iterKeys = refReverseMap.values().iterator(); 217 while (iterKeys.hasNext()) { 218 final FileSystemAndNameKey key = iterKeys.next(); 219 if (key.getFileSystem() == fileSystem) { 220 iterKeys.remove(); 221 files.remove(key.getFileName()); 222 } 223 } 224 225 if (files.size() < 1) { 226 close(fileSystem); 227 } 228 } finally { 229 lock.unlock(); 230 } 231 } 232 233 /** 234 * Called while the lock is held 235 * 236 * @param fileSystem The file system to close. 237 */ 238 private void close(final FileSystem fileSystem) { 239 if (log.isDebugEnabled()) { 240 log.debug("close fs: " + fileSystem.getRootName()); 241 } 242 243 fileSystemCache.remove(fileSystem); 244 if (fileSystemCache.size() < 1) { 245 endThread(); 246 } 247 /* 248 * This is not thread-safe as another thread might be opening the file system ((DefaultFileSystemManager) 249 * getContext().getFileSystemManager()) ._closeFileSystem(filesystem); 250 */ 251 } 252 253 @Override 254 public void close() { 255 super.close(); 256 257 endThread(); 258 259 lock.lock(); 260 try { 261 fileSystemCache.clear(); 262 263 refReverseMap.clear(); 264 } finally { 265 lock.unlock(); 266 } 267 } 268 269 @Override 270 public void removeFile(final FileSystem fileSystem, final FileName fileName) { 271 if (removeFile(new FileSystemAndNameKey(fileSystem, fileName))) { 272 close(fileSystem); 273 } 274 } 275 276 private boolean removeFile(final FileSystemAndNameKey key) { 277 if (log.isDebugEnabled()) { 278 log.debug("removeFile: " + this.getSafeName(key.getFileName())); 279 } 280 281 final Map<?, ?> files = getOrCreateFilesystemCache(key.getFileSystem()); 282 283 lock.lock(); 284 try { 285 final Object ref = files.remove(key.getFileName()); 286 if (ref != null) { 287 refReverseMap.remove(ref); 288 } 289 290 return files.size() < 1; 291 } finally { 292 lock.unlock(); 293 } 294 } 295 296 protected Map<FileName, Reference<FileObject>> getOrCreateFilesystemCache(final FileSystem fileSystem) { 297 if (fileSystemCache.size() < 1) { 298 startThread(); 299 } 300 301 Map<FileName, Reference<FileObject>> files; 302 303 do { 304 files = fileSystemCache.get(fileSystem); 305 if (files != null) { 306 break; 307 } 308 files = new HashMap<>(); 309 } while (fileSystemCache.putIfAbsent(fileSystem, files) == null); 310 311 return files; 312 } 313}