View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.vfs2.cache;
18  
19  import java.lang.ref.Reference;
20  import java.lang.ref.ReferenceQueue;
21  import java.lang.ref.SoftReference;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.concurrent.ConcurrentHashMap;
26  import java.util.concurrent.ConcurrentMap;
27  import java.util.concurrent.atomic.AtomicReference;
28  import java.util.concurrent.locks.Lock;
29  import java.util.concurrent.locks.ReentrantLock;
30  
31  import org.apache.commons.logging.Log;
32  import org.apache.commons.logging.LogFactory;
33  import org.apache.commons.vfs2.FileName;
34  import org.apache.commons.vfs2.FileObject;
35  import org.apache.commons.vfs2.FileSystem;
36  import org.apache.commons.vfs2.VfsLog;
37  import org.apache.commons.vfs2.util.Messages;
38  
39  /**
40   * This implementation caches every file as long as it is strongly reachable by
41   * the java vm. As soon as the vm needs memory - every softly reachable file
42   * will be discarded.
43   *
44   * @see SoftReference
45   */
46  public class SoftRefFilesCache extends AbstractFilesCache
47  {
48      private static final int TIMEOUT = 1000;
49  
50      private static final Log log = LogFactory.getLog(SoftRefFilesCache.class);
51  
52      private final ConcurrentMap<FileSystem, Map<FileName, Reference<FileObject>>> fileSystemCache =
53            new ConcurrentHashMap<FileSystem, Map<FileName, Reference<FileObject>>>();
54      private final Map<Reference<FileObject>, FileSystemAndNameKey> refReverseMap =
55            new HashMap<Reference<FileObject>, FileSystemAndNameKey>(100);
56      private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<FileObject>();
57  
58      private volatile SoftRefReleaseThread softRefReleaseThread = null; // @GuardedBy("lock")
59  
60      private final Lock lock = new ReentrantLock();
61  
62  
63      /**
64       * This thread will listen on the ReferenceQueue and remove the entry in the
65       * filescache as soon as the vm removes the reference
66       */
67      private final class SoftRefReleaseThread extends Thread
68      {
69          private volatile boolean requestEnd; // used for inter-thread communication
70  
71          private SoftRefReleaseThread()
72          {
73              setName(SoftRefReleaseThread.class.getName());
74              setDaemon(true);
75          }
76  
77          @Override
78          public void run()
79          {
80              loop: while (!requestEnd && !Thread.currentThread().isInterrupted())
81              {
82                  try
83                  {
84                      final Reference<?> ref = refQueue.remove(TIMEOUT);
85                      if (ref == null)
86                      {
87                          continue;
88                      }
89  
90                      lock.lock();
91                      try
92                      {
93                          final FileSystemAndNameKey key = refReverseMap.get(ref);
94  
95                          if (key != null && removeFile(key))
96                          {
97                              close(key.getFileSystem());
98                          }
99                      }
100                     finally
101                     {
102                         lock.unlock();
103                     }
104                 }
105                 catch (final InterruptedException e)
106                 {
107                     if (!requestEnd)
108                     {
109                         VfsLog.warn(getLogger(), log,
110                                     Messages.getString("vfs.impl/SoftRefReleaseThread-interrupt.info"));
111                     }
112                     break loop;
113                 }
114             }
115         }
116     }
117 
118     public SoftRefFilesCache()
119     {
120     }
121 
122     private void startThread()
123     {
124         // Double Checked Locking is allowed when volatile
125         if (softRefReleaseThread != null)
126         {
127             return;
128         }
129 
130         synchronized (lock)
131         {
132             if (softRefReleaseThread == null)
133             {
134                 softRefReleaseThread = new SoftRefReleaseThread();
135                 softRefReleaseThread.start();
136             }
137         }
138     }
139 
140     private void endThread()
141     {
142         synchronized (lock)
143         {
144             final SoftRefReleaseThread thread = softRefReleaseThread;
145             softRefReleaseThread = null;
146             if (thread != null)
147             {
148                 thread.requestEnd = true;
149                 thread.interrupt();
150             }
151         }
152     }
153 
154     @Override
155     public void putFile(final FileObject fileObject)
156     {
157         if (log.isDebugEnabled())
158         {
159             log.debug("putFile: " + this.getSafeName(fileObject));
160         }
161 
162         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
163 
164         final Reference<FileObject> ref = createReference(fileObject, refQueue);
165         final FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
166 
167         lock.lock();
168         try
169         {
170             final Reference<FileObject> old = files.put(fileObject.getName(), ref);
171             if (old != null)
172             {
173                 refReverseMap.remove(old);
174             }
175             refReverseMap.put(ref, key);
176         }
177         finally
178         {
179             lock.unlock();
180         }
181     }
182 
183     private String getSafeName(final FileName fileName)
184     {
185         return fileName.getFriendlyURI();
186     }
187 
188     private String getSafeName(final FileObject fileObject)
189     {
190         return this.getSafeName(fileObject.getName());
191     }
192 
193     @Override
194     public boolean putFileIfAbsent(final FileObject fileObject)
195     {
196         if (log.isDebugEnabled())
197         {
198             log.debug("putFile: " + this.getSafeName(fileObject));
199         }
200 
201         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
202 
203         final Reference<FileObject> ref = createReference(fileObject, refQueue);
204         final FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
205 
206         lock.lock();
207         try
208         {
209             if (files.containsKey(fileObject.getName()) && files.get(fileObject.getName()).get() != null)
210             {
211                 return false;
212             }
213             final Reference<FileObject> old = files.put(fileObject.getName(), ref);
214             if (old != null)
215             {
216                 refReverseMap.remove(old);
217             }
218             refReverseMap.put(ref, key);
219             return true;
220         }
221         finally
222         {
223             lock.unlock();
224         }
225     }
226 
227     protected Reference<FileObject> createReference(final FileObject file, final ReferenceQueue<FileObject> refqueue)
228     {
229         return new SoftReference<FileObject>(file, refqueue);
230     }
231 
232     @Override
233     public FileObject getFile(final FileSystem fileSystem, final FileName fileName)
234     {
235         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
236 
237         lock.lock();
238         try
239         {
240             final Reference<FileObject> ref = files.get(fileName);
241             if (ref == null)
242             {
243                 return null;
244             }
245 
246             final FileObject fo = ref.get();
247             if (fo == null)
248             {
249                 removeFile(fileSystem, fileName);
250             }
251             return fo;
252         }
253         finally
254         {
255             lock.unlock();
256         }
257     }
258 
259     @Override
260     public void clear(final FileSystem fileSystem)
261     {
262         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
263 
264         lock.lock();
265         try
266         {
267             final Iterator<FileSystemAndNameKey> iterKeys = refReverseMap.values().iterator();
268             while (iterKeys.hasNext())
269             {
270                 final FileSystemAndNameKey key = iterKeys.next();
271                 if (key.getFileSystem() == fileSystem)
272                 {
273                     iterKeys.remove();
274                     files.remove(key.getFileName());
275                 }
276             }
277 
278             if (files.size() < 1)
279             {
280                 close(fileSystem);
281             }
282         }
283         finally
284         {
285             lock.unlock();
286         }
287     }
288 
289     /**
290      * Called while the lock is held
291      * @param fileSystem The file system to close.
292      */
293     private void close(final FileSystem fileSystem)
294     {
295         if (log.isDebugEnabled())
296         {
297             log.debug("close fs: " + fileSystem.getRootName());
298         }
299 
300         fileSystemCache.remove(fileSystem);
301         if (fileSystemCache.size() < 1)
302         {
303             endThread();
304         }
305         /* This is not thread-safe as another thread might be opening the file system
306         ((DefaultFileSystemManager) getContext().getFileSystemManager())
307                 ._closeFileSystem(filesystem);
308          */
309     }
310 
311     @Override
312     public void close()
313     {
314         super.close();
315 
316         endThread();
317 
318         lock.lock();
319         try
320         {
321             fileSystemCache.clear();
322 
323             refReverseMap.clear();
324         }
325         finally
326         {
327             lock.unlock();
328         }
329     }
330 
331     @Override
332     public void removeFile(final FileSystem fileSystem, final FileName fileName)
333     {
334         if (removeFile(new FileSystemAndNameKey(fileSystem, fileName)))
335         {
336             close(fileSystem);
337         }
338     }
339 
340     private boolean removeFile(final FileSystemAndNameKey key)
341     {
342         if (log.isDebugEnabled())
343         {
344             log.debug("removeFile: " + this.getSafeName(key.getFileName()));
345         }
346 
347         final Map<?, ?> files = getOrCreateFilesystemCache(key.getFileSystem());
348 
349         lock.lock();
350         try
351         {
352             final Object ref = files.remove(key.getFileName());
353             if (ref != null)
354             {
355                 refReverseMap.remove(ref);
356             }
357 
358             return files.size() < 1;
359         }
360         finally
361         {
362             lock.unlock();
363         }
364     }
365 
366     protected Map<FileName, Reference<FileObject>> getOrCreateFilesystemCache(final FileSystem fileSystem)
367     {
368         if (fileSystemCache.size() < 1)
369         {
370             startThread();
371         }
372 
373         Map<FileName, Reference<FileObject>> files;
374 
375         do
376         {
377             files = fileSystemCache.get(fileSystem);
378             if (files != null)
379             {
380                 break;
381             }
382             files = new HashMap<FileName, Reference<FileObject>>();
383         } while (fileSystemCache.putIfAbsent(fileSystem, files) == null);
384 
385         return files;
386     }
387 }