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.atomic.AtomicReference;
028import java.util.concurrent.locks.Lock;
029import java.util.concurrent.locks.ReentrantLock;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.apache.commons.vfs2.FileName;
034import org.apache.commons.vfs2.FileObject;
035import org.apache.commons.vfs2.FileSystem;
036import org.apache.commons.vfs2.VfsLog;
037import 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 * @see SoftReference
045 */
046public class SoftRefFilesCache extends AbstractFilesCache
047{
048    private static final int TIMEOUT = 1000;
049
050    private static final Log log = LogFactory.getLog(SoftRefFilesCache.class);
051
052    private final ConcurrentMap<FileSystem, Map<FileName, Reference<FileObject>>> fileSystemCache =
053          new ConcurrentHashMap<FileSystem, Map<FileName, Reference<FileObject>>>();
054    private final Map<Reference<FileObject>, FileSystemAndNameKey> refReverseMap =
055          new HashMap<Reference<FileObject>, FileSystemAndNameKey>(100);
056    private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<FileObject>();
057
058    private volatile SoftRefReleaseThread softRefReleaseThread = null; // @GuardedBy("lock")
059
060    private final Lock lock = new ReentrantLock();
061
062
063    /**
064     * This thread will listen on the ReferenceQueue and remove the entry in the
065     * filescache as soon as the vm removes the reference
066     */
067    private final class SoftRefReleaseThread extends Thread
068    {
069        private volatile boolean requestEnd; // used for inter-thread communication
070
071        private SoftRefReleaseThread()
072        {
073            setName(SoftRefReleaseThread.class.getName());
074            setDaemon(true);
075        }
076
077        @Override
078        public void run()
079        {
080            loop: while (!requestEnd && !Thread.currentThread().isInterrupted())
081            {
082                try
083                {
084                    final Reference<?> ref = refQueue.remove(TIMEOUT);
085                    if (ref == null)
086                    {
087                        continue;
088                    }
089
090                    lock.lock();
091                    try
092                    {
093                        final FileSystemAndNameKey key = refReverseMap.get(ref);
094
095                        if (key != null && removeFile(key))
096                        {
097                            close(key.getFileSystem());
098                        }
099                    }
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}