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 *          2005) $
045 * @see SoftReference
046 */
047public class SoftRefFilesCache extends AbstractFilesCache
048{
049    private static final int TIMEOUT = 1000;
050    /**
051     * The logger to use.
052     */
053    private final Log log = LogFactory.getLog(SoftRefFilesCache.class);
054
055    private final ConcurrentMap<FileSystem, Map<FileName, Reference<FileObject>>> fileSystemCache =
056          new ConcurrentHashMap<FileSystem, Map<FileName, Reference<FileObject>>>();
057    private final Map<Reference<FileObject>, FileSystemAndNameKey> refReverseMap =
058          new HashMap<Reference<FileObject>, FileSystemAndNameKey>(100);
059    private final ReferenceQueue<FileObject> refQueue = new ReferenceQueue<FileObject>();
060
061    private final AtomicReference<SoftRefReleaseThread> softRefReleaseThread =
062            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                    final Reference<?> ref = refQueue.remove(TIMEOUT);
089                    if (ref == null)
090                    {
091                        continue;
092                    }
093
094                    lock.lock();
095                    try
096                    {
097                        final FileSystemAndNameKey key = refReverseMap.get(ref);
098
099                        if (key != null && removeFile(key)) 
100                        {
101                            close(key.getFileSystem());
102                        }
103                    }
104                    finally
105                    {
106                        lock.unlock();
107                    }
108                }
109                catch (final InterruptedException e)
110                {
111                    if (!requestEnd)
112                    {
113                        VfsLog.warn(getLogger(), log,
114                                    Messages.getString("vfs.impl/SoftRefReleaseThread-interrupt.info"));
115                    }
116                    break loop;
117                }
118            }
119        }
120    }
121
122    public SoftRefFilesCache()
123    {
124    }
125
126    private void startThread()
127    {
128        Thread thread;
129        SoftRefReleaseThread newThread;
130        do
131        {
132            newThread = null;
133            thread = softRefReleaseThread.get();
134            if (thread != null)
135            {
136                break;
137            }
138            newThread = new SoftRefReleaseThread();
139        } while (softRefReleaseThread.compareAndSet(null, newThread));
140        if (newThread != null)
141        {
142            newThread.start();
143        }
144    }
145
146    private void endThread()
147    {
148        final SoftRefReleaseThread thread = softRefReleaseThread.getAndSet(null);
149        if (thread != null)
150        {
151            thread.requestEnd = true;
152            thread.interrupt();
153        }
154    }
155
156    @Override
157    public void putFile(final FileObject fileObject)
158    {
159        if (log.isDebugEnabled())
160        {
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        {
172            final Reference<FileObject> old = files.put(fileObject.getName(), ref);
173            if (old != null)
174            {
175                refReverseMap.remove(old);
176            }
177            refReverseMap.put(ref, key);
178        }
179        finally
180        {
181            lock.unlock();
182        }
183    }
184
185    private String getSafeName(final FileName fileName)
186    {
187        return fileName.getFriendlyURI();
188    }
189
190    private String getSafeName(final FileObject fileObject)
191    {
192        return this.getSafeName(fileObject.getName());
193    }
194
195    @Override
196    public boolean putFileIfAbsent(final FileObject fileObject)
197    {
198        if (log.isDebugEnabled())
199        {
200            log.debug("putFile: " + this.getSafeName(fileObject));
201        }
202
203        final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileObject.getFileSystem());
204
205        final Reference<FileObject> ref = createReference(fileObject, refQueue);
206        final FileSystemAndNameKey key = new FileSystemAndNameKey(fileObject.getFileSystem(), fileObject.getName());
207
208        lock.lock();
209        try
210        {
211            if (files.containsKey(fileObject.getName()) && files.get(fileObject.getName()).get() != null)
212            {
213                return false;
214            }
215            final Reference<FileObject> old = files.put(fileObject.getName(), ref);
216            if (old != null)
217            {
218                refReverseMap.remove(old);
219            }
220            refReverseMap.put(ref, key);
221            return true;
222        }
223        finally
224        {
225            lock.unlock();
226        }
227    }
228
229    protected Reference<FileObject> createReference(final FileObject file, final ReferenceQueue<FileObject> refqueue)
230    {
231        return new SoftReference<FileObject>(file, refqueue);
232    }
233
234    @Override
235    public FileObject getFile(final FileSystem fileSystem, final FileName fileName)
236    {
237        final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
238
239        lock.lock();
240        try
241        {
242            final Reference<FileObject> ref = files.get(fileName);
243            if (ref == null)
244            {
245                return null;
246            }
247
248            final FileObject fo = ref.get();
249            if (fo == null)
250            {
251                removeFile(fileSystem, fileName);
252            }
253            return fo;
254        }
255        finally
256        {
257            lock.unlock();
258        }
259    }
260
261    @Override
262    public void clear(final FileSystem fileSystem)
263    {
264        final Map<FileName, Reference<FileObject>> files = getOrCreateFilesystemCache(fileSystem);
265
266        lock.lock();
267        try
268        {
269            final Iterator<FileSystemAndNameKey> iterKeys = refReverseMap.values().iterator();
270            while (iterKeys.hasNext())
271            {
272                final FileSystemAndNameKey key = iterKeys.next();
273                if (key.getFileSystem() == fileSystem)
274                {
275                    iterKeys.remove();
276                    files.remove(key.getFileName());
277                }
278            }
279
280            if (files.size() < 1)
281            {
282                close(fileSystem);
283            }
284        }
285        finally
286        {
287            lock.unlock();
288        }
289    }
290
291    /**
292     * Called while the lock is held
293     * @param fileSystem The file system to close.
294     */
295    private void close(final FileSystem fileSystem)
296    {
297        if (log.isDebugEnabled())
298        {
299            log.debug("close fs: " + fileSystem.getRootName());
300        }
301
302        fileSystemCache.remove(fileSystem);
303        if (fileSystemCache.size() < 1)
304        {
305            endThread();
306        }
307        /* This is not thread-safe as another thread might be opening the file system
308        ((DefaultFileSystemManager) getContext().getFileSystemManager())
309                ._closeFileSystem(filesystem);
310         */
311    }
312
313    @Override
314    public void close()
315    {
316        super.close();
317
318        endThread();
319
320        lock.lock();
321        try
322        {
323            fileSystemCache.clear();
324
325            refReverseMap.clear();
326        }
327        finally
328        {
329            lock.unlock();
330        }
331    }
332
333    @Override
334    public void removeFile(final FileSystem fileSystem, final FileName fileName)
335    {
336        if (removeFile(new FileSystemAndNameKey(fileSystem, fileName)))
337        {
338            close(fileSystem);
339        }
340    }
341
342    public void touchFile(final FileObject fileObject)
343    {
344    }
345
346    private boolean removeFile(final FileSystemAndNameKey key)
347    {
348        if (log.isDebugEnabled())
349        {
350            log.debug("removeFile: " + this.getSafeName(key.getFileName()));
351        }
352
353        final Map<?, ?> files = getOrCreateFilesystemCache(key.getFileSystem());
354
355        lock.lock();
356        try
357        {
358            final Object ref = files.remove(key.getFileName());
359            if (ref != null)
360            {
361                refReverseMap.remove(ref);
362            }
363
364            return files.size() < 1;
365        }
366        finally
367        {
368            lock.unlock();
369        }
370    }
371
372    protected Map<FileName, Reference<FileObject>> getOrCreateFilesystemCache(final FileSystem fileSystem)
373    {
374        if (fileSystemCache.size() < 1)
375        {
376            startThread();
377        }
378
379        Map<FileName, Reference<FileObject>> files;
380
381        do
382        {
383            files = fileSystemCache.get(fileSystem);
384            if (files != null)
385            {
386                break;
387            }
388            files = new HashMap<FileName, Reference<FileObject>>();
389        } while (fileSystemCache.putIfAbsent(fileSystem, files) == null);
390
391        return files;
392    }
393}