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    }