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