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.locks.Lock;
28  import java.util.concurrent.locks.ReentrantLock;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.commons.vfs2.FileName;
33  import org.apache.commons.vfs2.FileObject;
34  import org.apache.commons.vfs2.FileSystem;
35  import org.apache.commons.vfs2.VfsLog;
36  import org.apache.commons.vfs2.util.Messages;
37  
38  /**
39   * This implementation caches every file as long as it is strongly reachable by the java vm. As soon as the vm needs
40   * memory - every softly reachable file will be discarded.
41   *
42   * @see SoftReference
43   */
44  public class SoftRefFilesCache extends AbstractFilesCache {
45  
46      private static final int TIMEOUT = 1000;
47  
48      private static final Log log = LogFactory.getLog(SoftRefFilesCache.class);
49  
50      private final ConcurrentMap<FileSystem, Map<FileName, Reference<FileObject>>> fileSystemCache = new ConcurrentHashMap<>();
51      private final Map<Reference<FileObject>, FileSystemAndNameKey> refReverseMap = new HashMap<>(100);
52      private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<>();
53  
54      private volatile SoftRefReleaseThread softRefReleaseThread = null; // @GuardedBy("lock")
55  
56      private final Lock lock = new ReentrantLock();
57  
58      /**
59       * This thread will listen on the ReferenceQueue and remove the entry in the filescache as soon as the vm removes
60       * the reference
61       */
62      private final class SoftRefReleaseThread extends Thread {
63          private volatile boolean requestEnd; // used for inter-thread communication
64  
65          private SoftRefReleaseThread() {
66              setName(SoftRefReleaseThread.class.getName());
67              setDaemon(true);
68          }
69  
70          @Override
71          public void run() {
72              loop: while (!requestEnd && !Thread.currentThread().isInterrupted()) {
73                  try {
74                      final Reference<?> ref = refQueue.remove(TIMEOUT);
75                      if (ref == null) {
76                          continue;
77                      }
78  
79                      lock.lock();
80                      try {
81                          final FileSystemAndNameKey key = refReverseMap.get(ref);
82  
83                          if (key != null && removeFile(key)) {
84                              close(key.getFileSystem());
85                          }
86                      } finally {
87                          lock.unlock();
88                      }
89                  } catch (final InterruptedException e) {
90                      if (!requestEnd) {
91                          VfsLog.warn(getLogger(), log,
92                                  Messages.getString("vfs.impl/SoftRefReleaseThread-interrupt.info"));
93                      }
94                      break loop;
95                  }
96              }
97          }
98      }
99  
100     public SoftRefFilesCache() {
101     }
102 
103     private void startThread() {
104         // Double Checked Locking is allowed when volatile
105         if (softRefReleaseThread != null) {
106             return;
107         }
108 
109         synchronized (lock) {
110             if (softRefReleaseThread == null) {
111                 softRefReleaseThread = new SoftRefReleaseThread();
112                 softRefReleaseThread.start();
113             }
114         }
115     }
116 
117     private void endThread() {
118         synchronized (lock) {
119             final SoftRefReleaseThread thread = softRefReleaseThread;
120             softRefReleaseThread = null;
121             if (thread != null) {
122                 thread.requestEnd = true;
123                 thread.interrupt();
124             }
125         }
126     }
127 
128     @Override
129     public void putFile(final FileObject fileObject) {
130         if (log.isDebugEnabled()) {
131             log.debug("putFile: " + this.getSafeName(fileObject));
132         }
133 
134         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
135 
136         final Reference<FileObject> ref = createReference(fileObject, refQueue);
137         final FileSystemAndNameKeystemAndNameKey.html#FileSystemAndNameKey">FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
138 
139         lock.lock();
140         try {
141             final Reference<FileObject> old = files.put(fileObject.getName(), ref);
142             if (old != null) {
143                 refReverseMap.remove(old);
144             }
145             refReverseMap.put(ref, key);
146         } finally {
147             lock.unlock();
148         }
149     }
150 
151     private String getSafeName(final FileName fileName) {
152         return fileName.getFriendlyURI();
153     }
154 
155     private String getSafeName(final FileObject fileObject) {
156         return this.getSafeName(fileObject.getName());
157     }
158 
159     @Override
160     public boolean putFileIfAbsent(final FileObject fileObject) {
161         if (log.isDebugEnabled()) {
162             log.debug("putFile: " + this.getSafeName(fileObject));
163         }
164 
165         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
166 
167         final Reference<FileObject> ref = createReference(fileObject, refQueue);
168         final FileSystemAndNameKeystemAndNameKey.html#FileSystemAndNameKey">FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
169 
170         lock.lock();
171         try {
172             if (files.containsKey(fileObject.getName()) && files.get(fileObject.getName()).get() != null) {
173                 return false;
174             }
175             final Reference<FileObject> old = files.put(fileObject.getName(), ref);
176             if (old != null) {
177                 refReverseMap.remove(old);
178             }
179             refReverseMap.put(ref, key);
180             return true;
181         } finally {
182             lock.unlock();
183         }
184     }
185 
186     protected Reference<FileObject> createReference(final FileObject file, final ReferenceQueue<FileObject> refqueue) {
187         return new SoftReference<>(file, refqueue);
188     }
189 
190     @Override
191     public FileObject getFile(final FileSystem fileSystem, final FileName fileName) {
192         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
193 
194         lock.lock();
195         try {
196             final Reference<FileObject> ref = files.get(fileName);
197             if (ref == null) {
198                 return null;
199             }
200 
201             final FileObject fo = ref.get();
202             if (fo == null) {
203                 removeFile(fileSystem, fileName);
204             }
205             return fo;
206         } finally {
207             lock.unlock();
208         }
209     }
210 
211     @Override
212     public void clear(final FileSystem fileSystem) {
213         final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
214 
215         lock.lock();
216         try {
217             final Iterator<FileSystemAndNameKey> iterKeys = refReverseMap.values().iterator();
218             while (iterKeys.hasNext()) {
219                 final FileSystemAndNameKey key = iterKeys.next();
220                 if (key.getFileSystem() == fileSystem) {
221                     iterKeys.remove();
222                     files.remove(key.getFileName());
223                 }
224             }
225 
226             if (files.size() < 1) {
227                 close(fileSystem);
228             }
229         } finally {
230             lock.unlock();
231         }
232     }
233 
234     /**
235      * Called while the lock is held
236      *
237      * @param fileSystem The file system to close.
238      */
239     private void close(final FileSystem fileSystem) {
240         if (log.isDebugEnabled()) {
241             log.debug("close fs: " + fileSystem.getRootName());
242         }
243 
244         fileSystemCache.remove(fileSystem);
245         if (fileSystemCache.size() < 1) {
246             endThread();
247         }
248         /*
249          * This is not thread-safe as another thread might be opening the file system ((DefaultFileSystemManager)
250          * getContext().getFileSystemManager()).closeFileSystem(fileSystem);
251          */
252     }
253 
254     @Override
255     public void close() {
256         super.close();
257 
258         endThread();
259 
260         lock.lock();
261         try {
262             fileSystemCache.clear();
263 
264             refReverseMap.clear();
265         } finally {
266             lock.unlock();
267         }
268     }
269 
270     @Override
271     public void removeFile(final FileSystem fileSystem, final FileName fileName) {
272         if (removeFile(new FileSystemAndNameKey(fileSystem, fileName))) {
273             close(fileSystem);
274         }
275     }
276 
277     private boolean removeFile(final FileSystemAndNameKey key) {
278         if (log.isDebugEnabled()) {
279             log.debug("removeFile: " + this.getSafeName(key.getFileName()));
280         }
281 
282         final Map<?, ?> files = getOrCreateFilesystemCache(key.getFileSystem());
283 
284         lock.lock();
285         try {
286             final Object ref = files.remove(key.getFileName());
287             if (ref != null) {
288                 refReverseMap.remove(ref);
289             }
290 
291             return files.size() < 1;
292         } finally {
293             lock.unlock();
294         }
295     }
296 
297     protected Map<FileName, Reference<FileObject>> getOrCreateFilesystemCache(final FileSystem fileSystem) {
298         if (fileSystemCache.size() < 1) {
299             startThread();
300         }
301 
302         Map<FileName, Reference<FileObject>> files;
303 
304         do {
305             files = fileSystemCache.get(fileSystem);
306             if (files != null) {
307                 break;
308             }
309             files = new HashMap<>();
310         } while (fileSystemCache.putIfAbsent(fileSystem, files) == null);
311 
312         return files;
313     }
314 }